GYM102219F Military Class(状压DP)

题意:

给两个 1 - n的序列,要求序列中的数两两配对,使得配对的两个数绝对值之差小于 e ,并且还有 k 对限制,即 u 不能和 v 配对。

题解:

状压DP,

就是在N的值比较小的时候,可以用一个整数表示一行的DP状态,这个数的二进制反映了这一行的情况。

比如状态F(i, j)

假设第二维DP状态J的二进制形式是10001001,那么1就表示当前位置被访问,0就表示当前位置未访问。

因此,我们需要一些二进制操作来维护DP数组:

x>>1;//去掉末位数
x<<1;//在末尾增加一个0
x<<1|1;//在末尾增加一个1
x|1;//将末尾数置为1
x|1-1;//将末尾数置为0
x^1;//将末尾数取反
x|(1<<(i-1));//将从右往左第i个数变成1
x&~(1<<(i-1));//将从右往左第i个数变成0
x^(1<<(i-1));//将从右往左第i个数取反
x&(1<<i-1);//取后i位
x|(1<<i-1);//将后面i位变成1
x^(1<<i-1);//将后面i位取反
x|(x+1);//将末尾最后一个0变成1

观察到e的值也只有4,所以可以用状压表示配对到当前数时,已经用了9个数中的哪几个。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int mod=1e9+7;
typedef long long ll;
ll f[maxn][maxn],g[maxn][maxn],ans;
int n,e,k;
int main () {
    scanf("%d%d%d",&n,&e,&k);
    for (int i=0;i<k;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        g[x][y]=1;
    }
    f[0][0]=1;
    for (int i=1;i<=n;i++) 
        for (int x=0;x<(1<<2*e+1);x++)
            for (int j=-e;j<=e;j++) {
                int k=i+j;
                if (k<1||k>n||g[i][k]) continue;
                if ((x>>1)&(1<<(j+e))) continue;
                f[i][(x>>1)|(1<<(j+e))]=f[i][(x>>1)|(1<<(j+e))]+f[i-1][x];
                f[i][(x>>1)|(1<<(j+e))]%=mod;
            }
    for (int i=0;i<(1<<2*e+1);i++) ans+=f[n][i],ans%=mod;
    printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/zhanglichen/p/13764663.html