状压dp入门(模板题+思维题)

洛谷P1879 [USACO06NOV]玉米田Corn Fields

入门题

/*
洛谷P1879 
n*m矩阵 有些位置可选 有些不可选 不能选择两块相邻的土地
状压裸题:记录状态->判断矛盾->处理答案 
*/
#include<bits/stdc++.h>
using namespace std;
#define N 13
#define mod 100000000
int a[N][N],dp[N][1<<N],sta[1<<N];
bool get(int i)//判断是否有连续的 1 
{
    if(i&(i<<1)) return false;
    return true;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
          scanf("%d",&a[i][j]);
          if(a[i][j]) a[i][j]=0;
          else a[i][j]=1;
          sta[i]=(sta[i]<<1)+a[i][j];//sta为每一行的状态 
    }
    for(int i=0;i<(1<<m);i++) if(!(i&sta[1])&&get(i)) dp[1][i]++;//预处理第一行 
    for(int i=2;i<=n;i++)
     for(int j=0;j<(1<<m);j++)
      if(!(j&sta[i])&&get(j)){
          
          for(int k=0;k<(1<<m);k++){
              if((k&sta[i-1])||(j&k)||!get(k)) continue;//j&k判断是否两行有矛盾状态 
                dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
          }
           
      }
    int ans=0;
    for(int i=0;i<(1<<m);i++) ans=(dp[n][i]+ans)%mod;
    printf("%d\n",ans);
}
/*
2 3
1 1 1
0 1 0
*/

洛谷 P1896 [SCOI2005]互不侵犯

/*
洛谷P1896
题意:在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子
思路:判断是否有相邻的1 判断是否和上一行隔一个有一个1 
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 10
ll dp[N][1<<N][N*N];//多定义一维x 表示到这一行已经放了多少个国王
int get(int x)
{
    int ans=0;
    while(x){ if(x&1) ans++; x>>=1; }//求这一行的 1 的个数 
    return ans;
}
bool check(int x)
{
    if(x&(x<<1)) return false;//求有没有相邻的 1 
    return true;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<(1<<n);i++) if(check(i)) dp[1][i][get(i)]++; 
    for(int i=2;i<=n;i++)
      for(int j=0;j<(1<<n);j++)
       if(check(j)){
           for(int k=0;k<(1<<n);k++){
               if(!check(k)||(j&k)||j&(k<<1)||(j<<1)&k) continue;
               for(int x=0;x<=m;x++)
               dp[i][j][x+get(j)]+=dp[i-1][k][x];
           }
    }
    ll ans=0;
    for(int i=0;i<(1<<n);i++) ans+=dp[n][i][m];
    printf("%lld\n",ans);
}

洛谷P2051 [AHOI2009]中国象棋

/*
洛谷P2051 
题意:n*m的矩阵 每列最多放两个格子 每行最多放一个格子 求方案数
思路:状压,但n m太大 不能将所有状态压入  又
发现不必知道每一个详细状态 只需要知道每一行每一列放了多少个棋子
于是考虑维护列所放的状态(只维护放的个数) 枚举行进行dp转移
即固定行只选两个 对列进行转移 
*/
#include<bits/stdc++.h>
using namespace std;
#define mod 9999973
#define ll long long
#define N 105
//加强版状压 难点在于dp的定义
//定义 dp[i][j][k]为第i行 放了j列只有一个的格子 放了k列有两个的格子 
ll dp[N][N][N]; 
ll C(int n)
{
    return (n-1)*n/2;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    dp[0][0][0]=1;
    for(int i=0;i<=n-1;i++)//1不好先初始化 就由i转移到i+1 
     for(int j=0;j<=m;j++)
      for(int k=0;j+k<=m;k++)//注意范围!! j+k<=m 
      if(dp[i][j][k]){//排除无用状态 
          //每一行只能放两个棋子 分类讨论 
          dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%mod;//什么都不放 
          if(m-j-k>=1) dp[i+1][j+1][k]=(dp[i+1][j+1][k]+dp[i][j][k]*(m-j-k))%mod;//放一个在没有棋子的一列 
          if(j>=1)     dp[i+1][j-1][k+1]=(dp[i+1][j-1][k+1]+dp[i][j][k]*j%mod)%mod;//放一个在有一个棋子的一列 
          if(m-j-k>=2) dp[i+1][j+2][k]=(dp[i+1][j+2][k]+dp[i][j][k]*C(m-j-k)%mod)%mod;//放两个在没有棋子的两列 
          if(j>=2)     dp[i+1][j-2][k+2]=(dp[i+1][j-2][k+2]+dp[i][j][k]*C(j)%mod)%mod;//放两个在有棋子的两列
        if(m-j-k>=1&&j>=1) dp[i+1][j][k+1]=(dp[i+1][j][k+1]+dp[i][j][k]*(m-j-k)*j);
        //一个放没有棋子的 一个放有一个棋子的 
      }
    ll ans=0;
    for(int i=0;i<=m;i++)
     for(int j=0;i+j<=m;j++) 
      ans=(ans+dp[n][i][j])%mod;
    printf("%lld\n",ans);
}

待更新。。。

猜你喜欢

转载自www.cnblogs.com/mowanying/p/11228414.html