hoj2662 Pieces Assignment

Pieces Assignment

My Tags   (Edit)
  Source : zhouguyue
  Time limit : 1 sec   Memory limit : 64 M

Submitted : 684, Accepted : 235

Background

    有一个n*m的棋盘(n、m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。

Input

    本题有多组测试数据,每组输入包含三个正整数n,m和k。

Output

    对于每组输入,输出只有一个正整数,即合法的方案数。

Sample Input

2 2 3
4 4 1

Sample Output

0
16
/*
    对于每一行,如果把没有棋子的地方记为0,有棋子的地方记为1,那么每一行的状态都可以表示成一个2进制数,进而将其转化成10进制。
    那么这个问题的状态转移方程就变成了
    设dp[ i ] [ j ][k ]表示当前到达第i列,一共使用了j个棋子,且当前行的状态在压缩之后的十进制数为k 时的状态总数。那么我们也可以类似的写出状态转移方程:
     dp[ i ][ j ][ k ]=sum( dp[ i-1][ j-num(k) ][ w ] )   num(k)表示 k状态中棋子的个数,w表示前一行的状态。
    虽然写出了状态转移方程,但是还是有很多细节问题需要解决:比如,如何保证当前状态是合法的?
    最基本的做法是:首先判断k状态是否合法,也就是判断在这一行中是否有2个旗子相邻,然后枚举上一行的状态w,判断w状态是否合法,然后判断k状态和w状态上下之间是否有相邻的棋子。
    当然这样做的时间复杂度是很高的,也就是说有很多地方可以优化.
    比如:判断每一行状态是否合法,可以在程序一开始判断然后保存结果,判断k状态和w状态上下之间是否有相邻的棋子,可以利用位运算,if(k&w)说明上下之间有相邻的棋子等等。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,k,sum[1<<9],cnt,mark[1<<9];
long long dp[81][21][1<<9],ans;//dp[i][j][k]到第i行,共用了j个棋子,状态为k 
int count(int x){
    int res=0;
    while(x){
        if(x&1)res++;
        x>>=1;
    }
    return res;
}
int main(){
    freopen("Cola.txt","r",stdin);
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        ans=0;cnt=0;
        memset(dp,0,sizeof(dp));
        if(m>n)swap(m,n);
        for(int i=0;i<(1<<m);i++)
            if(!(i&(i<<1))){
                sum[++cnt]=count(i);
                mark[cnt]=i;
                dp[1][sum[cnt]][cnt]=1;
            }
        for(int i=2;i<=n;i++)
            for(int j=0;j<=k;j++)
                for(int t=1;t<=cnt;t++)
                    for(int p=1;p<=cnt;p++)
                        if(!(mark[t]&mark[p])&&j>=sum[t]){
                            dp[i][j][t]+=dp[i-1][j-sum[t]][p];
                        }
        for(int i=1;i<=cnt;i++){
            ans+=dp[n][k][i];
        }
        printf("%lld
",ans);
    }
}
100分 状压dp
原文地址:https://www.cnblogs.com/thmyl/p/7337590.html