zoukankan      html  css  js  c++  java
  • 状压DP

     [SCOI2005]互不侵犯

    在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。

      国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

    输入格式:

    只有一行,包含两个数n,m ( 1 <=n <=9, 0 <= m<= N * N)

    输出格式:

    所得的方案数

    初学者很天真地想写爆搜,但总感觉不妥,然后就听说这是状压DP……

    这怎么DP呀,要记录上一行是什么样吗?如何记录这么多状态?

    这就是状态压缩要解决的问题。

    对于每一行我们考虑用bool数组记录每一格的情况,1表示有,0表示没有,例如:10010101,表示第一,四,六,八格有国王。

    那么我们可以发现这个东西可以看做一个8位二进制数149,别告诉我149存不下。

    所以我们就可以将一行的状态压缩成一个二进制数,而处理这些状态就显然需要用位运算了。

    关于基础的位运算我们就不在赘述,这里介绍几个状压DP中常用的操作。

    S&(1<<i):判断第i位的情况,是0还是1.拿这道题来说就是状态S的第i格是不是有了国王。(需要注意的是i是从右往左数的)

    S|(1<<i):将S的第i位设置为1,往i这一位放上国王。

    S|~(1<<i):将S的第i位设置为0,往i这一位拿下国王(虽然这里不用)

    S1&S2:判断S1和S2是否有交集(对于某一位或几位i,有S1[i]==S2[i]==1),这里可以判断上下两行的国王会不会冲突。

    S1&(S2<<1):将S2左移一位再进行按位与,此时S1和S2是错位的,就可以用来判断右上和左下的冲突。

    S1&(S2>>1):同上,判断左上和右下的冲突。

    注:弄不清运算符优先级的同学们一定要多加括号。

    科普完我们就该讲一下这道题的思路了(^_^),我们令f[i][j][k],表示第i行,状态为j且前i行已放置k个国王的方案数。

    那么显然f[i][j][k]=∑f[i-1][j′][k′]

    其中k=k'+状态j的国王数。

    最终ans=Σf[n][i][m]

    我们可以预处理出所有状态以及该状态的国王数。

    代码如下:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,m;
    long long f[10][150][150],ans;
    int num[150],s[150],total;
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<(1<<n);i++)
        {
            if(i&(i<<1))continue;//检查当前状态是否冲突 
            int k=0;
            for(int j=0;j<n;j++)
            if(i&(1<<j))//数一数这个状态需要多少国王 
            k++;
            s[++total]=i;//记录状态 
            num[total]=k;//国王个数 
        }
        f[0][1][0]=1;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=total;j++)
        for(int k=0;k<=m;k++)
        if(k>=num[j])
        for(int t=1;t<=total;t++)//第i-1行可能是什么 
        if(!(s[t]&s[j])&&!(s[t]&(s[j]<<1))&&!(s[t]&(s[j]>>1)))//上下无冲突。 
        f[i][j][k]+=f[i-1][t][k-num[j]];
        for(int i=1;i<=total;i++)
        ans+=f[n][i][m];
        cout<<ans;
    }

     请原谅本蒟蒻现学现卖QWQ

    然后我又找来了另一道题:

    [USACO06NOV]玉米田Corn Fields

    貌似也是一道裸题,那不妨我们就用这道题来总结套路:

    令f[i][j],表示第i行j种状态的方案数。

    首先预处理每一行可能的状态,我们要保证左右不重复,且不能种在贫瘠的土地上。

    对于每一行i的每一种状态j,考虑前一行的每种状态k,若不冲突,则令f[i][j]+=f[i-1][k].

    最终统计所有f[n],(习惯上令n表示行数,m表示列数)

    这就是刚学习状压的蒟蒻的感受:

    先预处理状态,对于每种状态考虑是否与前一状态重复并递推答案。最后统计终点的所有状态的答案之和。

     1 #include<iostream>
     2 #include<algorithm>
     3 #include<cstring>
     4 #include<cstdio>
     5 using namespace std;
     6 const int mod=100000000;
     7 int n,m,f[20][1<<20];
     8 struct cym{
     9     int s[1<<20],num;
    10 }a[15];
    11 void find(int S,int now)
    12 {
    13     int total=0;
    14     for(int i=0;i<(1<<m);i++)
    15     if(!(i&(i<<1))&&!(i&(i>>1))&&!(i&S))//我们可以用左右移与自身匹配来判断左右冲突。 
    16     a[now].s[++total]=i;//now来表示土地情况,状态不与now冲突。 
    17     a[now].num=total;
    18 }
    19 int main()
    20 {
    21     scanf("%d%d",&n,&m);
    22     for(int i=1;i<=n;i++)
    23     {
    24         int now=0;
    25         for(int j=1;j<=m;j++)
    26         {
    27             int x;
    28             scanf("%d",&x);
    29             now=(now<<1|1)-x;
    30         }
    31         find(now,i);
    32     }
    33     for(int i=1;i<=a[1].num;i++)
    34     f[1][i]=1;
    35     for(int i=2;i<=n;i++)
    36     for(int j=1;j<=a[i].num;j++)
    37     for(int k=1;k<=a[i-1].num;k++)
    38     if(!(a[i].s[j]&a[i-1].s[k]))//与前一状态是否冲突 
    39     {
    40         f[i][j]+=f[i-1][k];
    41         f[i][j]%=mod;
    42     }
    43     int ans=0;
    44     for(int i=1;i<=a[n].num;i++)
    45     ans=(ans+f[n][i])%mod;
    46     printf("%d",ans);
    47 }
  • 相关阅读:
    【POI 2007】Office 办公楼(BIU)
    【Codeforces #130 Div2】Solutions
    【TopCoder SRM 551 Div2】Solutions
    【POI 2007】Axes of Symmetry 对称轴(osi)
    【HDU 2222】Keywords Search
    【POI 2007】Tetris Attack 正方体大作战(tet)
    【SGU 101】Domino
    【JSOI 2009】游戏 Game
    【SGU 102】Coprimes
    【Codeforces #133 Div2】Solutions
  • 原文地址:https://www.cnblogs.com/zzh666/p/9223145.html
Copyright © 2011-2022 走看看