状压DP入门例题

互不侵犯King

/*
互不侵犯King
HYSBZ - 1087
https://cn.vjudge.net/problem/HYSBZ-1087
题意:n×n的矩阵里放k个国王,每个国王的周边8个格子不能放其他国王,问有有多少方法
解法:
f[i][j][l] 表示前i行状态为j上有k个国王时的最优解
状压dp,用数字的二进制来表示这一行上的国王分布
然后记录在一行中,可以有效表达国王分布的数字并记录
设当前行的状态为j,上一行的状态为x,可以得到下面的转移方程: 
f[i][j][l]=f[i-1][x][l-sta[x]]
*/
#include <algorithm>
#include <iostream>
using namespace std;
#define maxn 2005
long long sit[maxn];//记录一行中所有的合法状态
long long sta[maxn];//记录合法状态下的国王数
long long f[15][maxn][105];//f[i][j][l] 表示前i行状态为j上有k个国王时的最优解
int n, k, cnt;
void dfs(int x,int num,int cur){//x表示这一行的状态 num记录国王数 cur记录状态长度
  if(cur>=n){
    sit[++cnt]=x;
    sta[cnt]=num;
    return ;
  }
  dfs(x,num,cur+1);//cur位置不放国王
  dfs(x+(1<<cur),num+1,cur+2);//cur位置放国王,与它相邻的位置不能再放国王
}
int main(){
  cin>>n>>k;
  dfs(0,0,0);//先预处理一行的所有合法状态
  for(int i=1; i<=cnt;i++) f[1][i][sta[i]]++;
  for(int i=2;i<=n;i++){
    for(int j=1;j<=cnt;j++){
      for(int l=1;l<=cnt;l++){
        if(sit[j]&sit[l]) continue;
        if((sit[j]<<1)&sit[l]) continue;
        if(sit[j]&(sit[l]<<1)) continue;
        //排除不合法转移
        for(int p=sta[j];p<=k;p++){
          f[i][j][p]+=f[i-1][l][p-sta[j]];
        }
      }
    }
  }
  long long ans=0;
  for(int i=1;i<=cnt;i++) ans+=f[n][i][k];
  cout<<ans<<endl;
  return 0;
}

炮兵阵地
/*
炮兵阵地
P2704
https://www.luogu.org/problem/P2704
解法:
状压DP
由于当前行和前两行有关系,所以得用3维矩阵来保存一个状态下最多的炮兵个数
dp[i][curst][prest]表示当前第i行状态对curst,前一行状态为prest的最大炮兵数
dp[i][curst][prest]=max{dp[i-1][prest][preprest]
这样求到最后一行之后,答案就是最后一行所有状态中最大的那个。
程序初始化的时候需要对第一行
*/
#include <bits/stdc++.h>
using namespace std;
int dp[115][70][70];
int maze[105];
int sit[70];
int sta[70];
int n,m,len;
char str[111];
int main(){
  while(cin>>n>>m){
    memset(dp,0,sizeof dp);
    memset(maze,0,sizeof maze);
    memset(sit,0,sizeof sit);
    memset(sta,0,sizeof sta);
    for(int i=1;i<=n;i++){
      scanf("%s", str);
      for(int j=0;j<m;j++){
        if(str[j]=='H'){
          maze[i]+=(1<<j);
        }
      }
    }
    len=0;
    for(int i=0;i<(1<<m);i++){
      if(!(i&(i<<1)) && !(i&(i<<2))){
        int k=i,sum=0;
        while(k){
          if(k&1) sum++;
          k>>=1;
        }
        sta[++len]=sum;
        sit[len]=i;
      }
    }
    for(int i=1;i<=len;i++){
      if(!(sit[i]&maze[1])){
        dp[1][i][1]=sta[i];
      }
    }
    for(int i=2;i<=n;i++){
      for(int j=1;j<=len;j++){
        if(sit[j]&maze[i]) continue;
        for(int k=1;k<=len;k++){
          if(sit[k]&sit[j]) continue;
          if(sit[k]&maze[i-1]) continue;
          for(int t=1;t<=len;t++){
            if(sit[t]&sit[j]) continue;
            if(sit[t]&sit[k]) continue;
            if(sit[t]&maze[i-2]) continue;
            dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+sta[j]);
          }
        }
      }
    }
    int ans=0;
    for(int i=1;i<=len;i++){
      for(int j=1;j<=len;j++){
        ans=max(ans,dp[n][i][j]);
      }
    }
    cout<<ans<<endl;
  }
  return 0;
}

玉米田Corn Fields
/*
玉米田Corn Fields
P1879
https://www.luogu.org/problem/P1879
题面:
有一个N×M的田地,0表示贫瘠,1表示肥沃,
只能在肥沃的土地上种玉米,
并且每一颗玉米都不能相邻,问有多少种方法
解法:
状压DP
*/
#include <bits/stdc++.h>
using namespace std;
#define maxn 10000
#define MOD 100000000
long long dp[15][maxn];
long long sit[maxn];
long long maze[maxn];
int n,m,a,len;
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    for(int j=0;j<m;j++){
      cin>>a;
      a=1-a;
      maze[i]+=(a<<j);
    }
  }
  len=0;
  for(int i=0;i<(1<<m);i++){
    if(!(i&(i<<1))){
      sit[++len]=i;
    }
  }
  dp[0][1]=1;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=len;j++){
      if(sit[j]&maze[i]) continue;
      for(int k=1;k<=len;k++){
        if(sit[k]&sit[j]) continue;
        if(sit[k]&maze[i-1]) continue;
        dp[i][j]+=dp[i-1][k];
        dp[i][j]%=MOD;
      }
    }
  }
  long long ans=0;
  for(int i=1;i<=len;i++){
    ans+=dp[n][i];
    ans%=MOD;
  }
  cout<<ans<<endl;
  return 0;
}

发布了70 篇原创文章 · 获赞 22 · 访问量 6508

猜你喜欢

转载自blog.csdn.net/weixin_44410512/article/details/100058465