• 洛谷 P1763 状态压缩dp+容斥原理


    (题目来自洛谷oj)

    一天,maze决定对自己的一块n*m的土地进行修建。他希望这块土地共n*m个格子的高度分别是1,2,3,...,n*m-1,n*m。maze又希望能将这一些格子中的某一些拿来建蓄水池,即这个格子的高度应该比它周围8个格子的高度都小(超出土地范围的格子的高度算作无穷大)。现在,请你帮maze计算:他有多少种不同的修建土地的方案数?

    (请你将方案数对12345678取模)

    输入

    输入第一行两个数字n,m。

    接下来N行,每行M个字符,’.’表示普通格子,’X’表示蓄水池。

    输出

    仅一行,包含一个数字,为取模后的方案数。

    一组输入输出大概长这样:

    输入:                       输出:

    3 5                        5851854
    .X...
    ....X
    X....

    题意抽象:

        给定一个n*m个格子矩形,现在要将1,2,3....n*m个数字填入格子。

        问有且仅有指定格子满足条件p的方案数。

        条件p:该格子中的数比邻近8个格子中的数都小。

        数据范围1≤n≤4,m≤7。

    首先数据范围挺小,最多28个格子,最多能不能有8个蓄水池。

    于是想着搜一下。

    但仔细想想发现还真是挺复杂的。

    有几个关键点:

    1.从小往大放数讨论方案数。这样的话如果“X”处已经放数了,那么它的旁边都可以放数了。否则它周围禁止放数。

    2.不宜直接讨论当且仅当X处是蓄水池的方案数。讨论这样的方案数:保证题中所给的k个X处是蓄水池,但其他地方也可能是蓄水池的方案数。然后容斥。

    这个容斥略有难想。

    设目标状态S0中有k个蓄水池。设Ak为至少这k个X处是蓄水池的方案数。

    先一次calc(S0)求出AS0。

    然后要把其他地方还有蓄水池的方案数减掉。

    于是往S0上加蓄水池(把某一个'.'变成‘X’),设加一个得到的状态S1.

    由于加蓄水池的地方不同,会有很多种S1,我们记它们为S1,0...S1,i....S1,q

    我们要减去的是AS1,0 ....AS1,i...AS1,q的并集的元素数目。

    到这里容斥就很明显了。

    更多具体细节在代码中注释:

    #include<bits/stdc++.h>
    #define rep(i,a,b) for(int i=a;i<=b;++i)
    using namespace std;
    const int mo=12345678;
    int n,m,ans,tp;
    char graph[10][10];
    bool use[10][10];
    int f[1<<9][50];
    int ax[20],ay[20];
    void Input()
    {
        scanf("%d%d
    ",&n,&m);
        rep(i,0,n-1)
        {
            scanf("%s",graph[i]);
        }
        ans=0;
    }
    bool inrange(int x,int y)
    {
        return x>=0&&y>=0&&x<n&&y<m;
    }
    int calc()
    {
        tp=0;
        memset(f,0,sizeof(f));
        f[0][0]=1;
        rep(i,0,n-1)
        {
            rep(j,0,m-1)
            {
                if(graph[i][j]=='X')
                {
                    ax[tp]=i;ay[tp]=j;tp++;
                }
            }
        }
        for(int s=0;s<(1<<tp);++s)
        {
            memset(use,1,sizeof(use));
            rep(i,0,tp-1)
            {
                if(!(s&(1<<i)))             //如果这个X还没有放数
                rep(dx,-1,1)
                {
                    rep(dy,-1,1)
                    {
                        if(inrange(ax[i]+dx,ay[i]+dy)) use[ax[i]+dx][ay[i]+dy]=0;
                    }
                }
            }
            int cnt=0;              //cnt为可以随便放的位置的个数
            rep(i,0,n-1)
            {
                rep(j,0,m-1) if(use[i][j]) ++cnt;
            }
            rep(i,0,cnt)                //数是从小往大放,只有X位置上已经放数了之后周围才能放数
            {
                if(f[s][i])
                {
                    f[s][i+1]=(f[s][i+1]+f[s][i]*(cnt-i))%mo;      //往无影响区域放数
                    rep(j,0,tp-1)
                    {
                        if(!(s&(1<<j)))
                        {
                            f[s|(1<<j)][i+1]=(f[s|(1<<j)][i+1]+f[s][i])%mo;     //往X上放数
                        }
                    }
                }
            }
        }
        return f[(1<<tp)-1][n*m];
    }
    void go(int x,int y,int k)
    {
    
        if(x>=n) ans=(ans+k*calc())%mo;
        else if(y>=m) go(x+1,0,k);
        else
        {
            go(x,y+1,k);
            bool ok=1;
            rep(dx,-1,1)
            {
                rep(dy,-1,1)
                {
                    if(inrange(x+dx,y+dy)&&graph[x+dx][y+dy]=='X') ok=0;
                }
            }
            if(ok)          //如果ok为0,这个点不能再设蓄水池了(注意此时(x,y)处必定不为题中所给蓄水池位置之一)
            {
                graph[x][y]='X';
                go(x,y+1,-k);                //容斥。一条go路径得到的答案是'X'都满足是蓄水池但'.'处也可能是蓄水池的方案数
                graph[x][y]='.';
            }
        }
    }
    int solve()
    {
        rep(i,0,n-1)
        {
            rep(j,0,m-1)
            {
               // printf("i=%d j=%d %c
    ",i,j,graph[i][j]);
                if(graph[i][j]=='X')
                    rep(dx,-1,1)
                    {
                        rep(dy,-1,1)
                        {
                            if((dx||dy)&&inrange(i+dx,j+dy)&&graph[i+dx][j+dy]=='X') return 0;
                        }
                    }
            }
        }
        ans=0;
        go(0,0,1);
        ans=(ans+mo)%mo;
        return ans;
    }
    int main()
    {
       // freopen("in.txt","r",stdin);
        Input();
        printf("%d
    ",solve());
        return 0;
    }
  • 相关阅读:
    单片机多功能调试助手
    《划时代51单片机C语言全新教程》第十九章 网络通信 概览
    《划时代51单片机C语言全新教程》第十五章 按键计数器 概览
    《划时代51单片机C语言全新教程》第十七章 频率计 概览
    前后台程序框架实例2
    《划时代51单片机C语言全新教程》第二十一章 深入编程 概览
    《划时代51单片机C语言全新教程》第二十二章 界面开发 概览
    《划时代51单片机C语言全新教程》第十八章 USB通信 概览
    《划时代51单片机C语言全新教程》第二十章 深入接口 概览
    《划时代51单片机C语言全新教程》第十六章 交通灯 概览
  • 原文地址:https://www.cnblogs.com/zhixingr/p/7825832.html
Copyright © 2020-2023  润新知