【题解】互不侵犯 SCOI 2005 BZOJ 1087 插头dp
以前没学插头dp的时候觉得这题贼难,根本不会做,学了才发现原来是一裸题。
用二进制表示以前的格子的状态,0表示没放国王,1表示放了国王。
假设当前位置为(x,y),需要记录的是(x-1,y-1)至(x,y-1)的信息,共n+1个点。
每个状态有两种决策,第一种是这个格子不放国王,直接转移。
第二种是这个格子放国王,需要满足几个条件才能进行这步转移,条件很显然,具体见代码和注释。
因为状态数很少,所以也用不到BFS转移状态和Hash的技巧,所以这题还是很清真的,细节不多,码量也很小。
#include <cstring> #include <algorithm> #include <cstdio> using namespace std; typedef long long ll; const int MAXN = 9; int n, k; inline int getd( int x, int d ) { // 位运算相关 return (x>>d)&1; } inline int setd( int x, int d, int v ) { return (x&(~(1<<d)))|(v<<d); } ll f[2][81][1024]; // 滚动数组 void update_no( int cur, int kk, int S ) { // 不放国王 int nS = setd(S,n,0)<<1; f[cur^1][kk][nS] += f[cur][kk][S]; } void update_yes( int cur, int kk, int S ) { // 放国王 int nS = (setd(S,n,0)<<1)|1; f[cur^1][kk+1][nS] += f[cur][kk][S]; } void solve() { int cur = 0; f[cur][0][0] = 1; for( int i = 0; i < n; ++i ) for( int j = 0; j < n; ++j ) { memset( f[cur^1], 0, sizeof(f[cur^1]) ); for( int kk = 0; kk <= k; ++kk ) for( int S = 0; S < 1024; ++S ) if( f[cur][kk][S] ) { update_no(cur,kk,S); // 任何格子都可以不放 if( ( !j || !getd(S,0) ) && // 在第一列或者左边没有国王 ( !i || !getd(S,n-1) ) && // 在第一行或者上面没有国王 ( !j || !i || !getd(S,n) ) && // 在第一行或者在第一列或者左上角没有国王 ( j == n-1 || !i || !getd(S,n-2) ) && // 在最后一列或者在第一行或者右上角没有国王 kk < k ) // 还有剩余的国王可以放 update_yes(cur,kk,S); } cur ^= 1; } ll ans = 0; for( int S = 0; S < 1024; ++S ) ans += f[cur][k][S]; printf( "%lld\n", ans ); } int main() { scanf( "%d%d", &n, &k ), solve(); return 0; }