首页 > 代码库 > 【题解】互不侵犯 SCOI 2005 BZOJ 1087 插头dp

【题解】互不侵犯 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;
}

 

【题解】互不侵犯 SCOI 2005 BZOJ 1087 插头dp