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; }
当你意识到,每个上一秒都成为永恒。