状态压缩DP做题笔记(一)
POJ1321
中文题目,这里就不再解释了
以前DFS做过,这次也先顺手写了个DFS的
然后DE了1小时BUG
#include <iostream>
#include <cstring>
#define N 10100
#define INF 0x3f3f3f3f
#define LL long long
#define eps 1e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
int n,ans;
char mp[9][9];
bool vis[9];
void dfs(int cur,int row,int num);
int main()
{
ios::sync_with_stdio(false);
int k;
while(cin>>n>>k&&n!=-1){
ans=0;
mem(vis,0);
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
cin>>mp[i][j];
}
}
dfs(0,0,k);
cout<<ans<<endl;
}
return 0;
}
void dfs(int cur,int row,int num)
{
if(cur==num){
++ans;
return;
}
for(int i=row;i<n-num+cur+1;++i){
for(int j=0;j<n;++j){
if(mp[i][j]=='#'&&!vis[j]){
vis[j]=true;
dfs(cur+1,i+1,num);
vis[j]=false;
}
}
}
}
然后这题的状态压缩是怎么想的呢
首先正如我们DFS直接用了一个vis[n]来记录被用掉的列一样,最后的,我们记录状态的时候所用dp[i][j]中的j的二进制表示的就是取了哪些列,状态转移方程为
最后数一遍状态为所需棋子个数的状态数就好了
#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
#include <vector>
#include <queue>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <bitset>
#define INF 0x3f3f3f3f
#define ULL unsigned long long
#define LL long long
#define N 10100
#define eps 10e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
const double PI=acos(-1.0);
int n,k;
char mp[10][10];
int legal[1000];
int d[10][1<<10];
void cal(int sta){
bitset<10> a(sta);
legal[sta]=a.count();
}
int main()
{
ios::sync_with_stdio(false);
for(int i=0;i<(1<<8);++i){
cal(i);
}
while(cin>>n>>k&&n!=-1){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
cin>>mp[i][j];
}
}
mem(d,0);
d[0][0]=1;//不放子就能达到该状态,不能忘
int sta=1<<n,news;
for(int i=1;i<=n;++i){
for(int j=0;j<sta;++j){
d[i][j]+=d[i-1][j];//不放棋子就能达到该状态
for(int u=1;u<=n;++u){
if(mp[i][u]=='#'&&((j&(1<<(u-1)))==0)){
news=j|(1<<(u-1));
d[i][news]+=d[i-1][j];//如果能放棋子,那么可行解就加上能到这个子状态的解
}
}
}
}
int ans=0;
for(int i=0;i<sta;++i){
if(legal[i]==k){
ans+=d[n][i];
}
}
cout<<ans<<endl;
}
return 0;
}
POJ3254
题目大意就是在一块区域内选出不限个数的格子(至少为1)且格子间互不相连,问有多少种选法。区域内有部分不能选择。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
#include <vector>
#include <queue>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <bitset>
#define INF 0x3f3f3f3f
#define ULL unsigned long long
#define LL long long
#define N 10100
#define MOD 100000000
#define eps 10e-9
#define mem(a,n) memset(a,n,sizeof(a))
#define fread freopen("in.txt","r",stdin)
#define fwrite freopen("out.txt","w",stdout)
using namespace std;
const double PI=acos(-1.0);
int m,n,uppr;
int way[600];
int dp[15][600];
int cur[15];
inline bool ok(int x){//若此状态没有两个1相邻,则可行
return !(x&(x<<1));
}
void init()
{
uppr=0;
int mx=1<<n;
for(int i=0;i<mx;++i){
if(ok(i)){
way[uppr++]=i;//预处理出所有可行的组合,因为上界在运行时确定可以节省掉DP时候的冗余,所以此处的init()是针对每一次运行重新调用以确定上界
}
}
}
int main()
{
int temp;
ios::sync_with_stdio(false);
while(cin>>m>>n){
init();
mem(dp,0);
for(int i=1;i<=m;++i){
cur[i]=0;
for(int j=1;j<=n;++j){
cin>>temp;
if(temp==0){
cur[i]|=(1<<(n-j));//把每一行能走的位置标出来,这里采用相反的原因是如果此处是0则此处也不可行,但是不处理就会被忽略掉,具体看之后的代码
}
}
}
for(int i=0;i<uppr;++i){
if(!(way[i]&cur[1])){
dp[1][i]=1;//因为第一排没有子问题,单独处理
}
}
for(int i=2;i<=m;++i){
for(int j=0;j<uppr;++j){
if((way[j]&cur[i])!=0){
continue;
}
for(int k=0;k<uppr;++k){
if(((way[k]&cur[i-1])!=0)||((way[k]&way[j])!=0)){//若能放在这一排并且和上一排不冲突,就做下面的,否证continue。就是为了此处能用一个&处理掉所以前面那样写
continue;
}
dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;//状态转移
}
}
}
int ans=0;
for(int i=0;i<uppr;++i){
ans=(ans+dp[m][i])%MOD;
}
cout<<ans<<endl;
}
return 0;
}
感觉还没体会到真谛,之后慢慢补齐……