SCOI2005 互不侵犯

传送门

这道题看数据范围<=9,很容易想到是状压DP。

根据各位dalao的讲述,状压DP中经常把每一个行的情况都压缩起来,之后进行DP。那么这样的话想起DP的状态就比较简单,dp[i][j][s]表示枚举到第i行,第i行的状态为j,算上当前行一共放了s个国王一共有多少种情况。其中j是一个二进制串,某一位是1代表该位有国王,否则没有。

那么dp方程就是: dp[i][j][s] = sum{dp[i-1][k][s-num[j]]},其中k是上一行能取到的状态,num指的是本行这种状态(j)中一共有几个国王。其中i,j,k,s都是枚举的。

不过算来直接枚举会超时……思考一下,因为国王是互不侵犯的,所以对于每一行来说,肯定有很多种状态是根本不可选择的。那我们先预处理出所有可能存在的情况再去枚举。方法很简单,对于状态i,如果i&(i<<1),那么就说明当前的状态是不可选择的。直接排除掉即可。

之后,对于每两行的情况,我们仿照上面的做法&一下即可。比如当前两行的状态为x,y,如果x&y || x&(y<<1)|| (x<<1 )& y说明这两种状态之间是不可转移的(因为会互相侵犯)

这样进行转移就可以了,注意要预处理第一行的所有情况。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
const int M = 10;
typedef long long ll;

ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
ll n,k,dp[M][1<<M][M<<2],gk[1<<M],ans,cnt,num[1<<(M+1)];

ll getsum(ll x)
{
    ll cur = 0;
    while(x) cur += (x&1),x >>= 1;
    return num[cnt] = cur;
}
int main()
{
    n = read(),k = read();
    rep(i,0,(1<<n)-1) if(!(i&(i<<1))) gk[++cnt] = i,dp[1][cnt][getsum(i)] = 1;//第一行的预处理,同时处理出所有可行状态
    rep(i,2,n)
    rep(j,1,cnt)
    {
        ll x = gk[j];
        rep(p,1,cnt)
        {
            ll y = gk[p];
            if((x & y) || (x & (y << 1)) || ((x << 1) & y)) continue;//排除不可转移的情况
            rep(q,0,k) if(q >= num[j]) dp[i][j][q] += dp[i-1][p][q-num[j]];//把可行的情况加上即可
        }
    }
    rep(i,1,cnt) ans += dp[n][i][k];
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2018-08-29 22:24  CaptainLi  阅读(151)  评论(0编辑  收藏  举报