【浮*光】#状态压缩# 状压DPの相关练习题

T1:【p2704】炮兵阵地

  • 一个N*M的地图,每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示)。
  • 在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队)。
  • 每个部队能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
  • 两支炮兵部队之间不能互相攻击,即任何炮兵部队都不在其他部队的攻击范围内。
  • 在整个地图区域内最多能够摆放多少我军的炮兵部队。

这题确实是状压dp的经典啊...确实细节也算不太好处理...

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

//用每个m位二进制数表示一行的状态:
//每个二进制数的第k位为1表示在第k列上放置部队。

/*【p2704】炮兵阵地
一个N*M的地图,每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示)。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队)。
每个部队能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
两支炮兵部队之间不能互相攻击,即任何炮兵部队都不在其他部队的攻击范围内。
在整个地图区域内最多能够摆放多少我军的炮兵部队。*/

//预处理出集合S,储存“相邻两个1的距离不小于3”的所有M位二进制数。

int sums[(1<<10)]; //sums[x]表示x的二进制表示中1的个数//int ff[109][1<<11][1<<11]; //2048*2048*109??? ---> 所以要用滚动数组啊qwq
//f[i][j][k]表示第i-1行的状态是j,第i行状态是k时,前i行最多放置的炮兵数。

int f[3][(1<<11)][(1<<11)]={0}; //【滚动数组】记录前两行的状态即可,即(%3=)0,1,2 。

int n,m,a[109],anss=0; //a[109]记录每行的初始可行状态(是二进制数转化为十进制的数)

void reads(int &x){ int fx=1;x=0;char s=getchar();
  while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
  while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=fx; }

int getsum(int S){ //当前状态 S 里面包含几个 1
  int tot=0; while(S){if(S&1) tot++; S>>=1;} return tot;
} //【坑点!!】这个一定要写成函数,要不然会TLE...

int main(){
  reads(n); reads(m); char ss;
  
  for(int i=0;i<n;i++) //注意,状压DP中最好都用0开始
    for(int j=0;j<m;j++) //用a[i]记录每行的初始可行状态
      cin>>ss,a[i]<<=1,a[i]+=(ss=='H'?1:0); 
    //二进制数a[i]每次向前移一位,记录第i行中障碍的位置

  memset(sums,0,sizeof(sums)); //记录每种行状态要放置的部队数
  for(int i=0;i<(1<<m);i++) sums[i]=getsum(i);

  for(int i=0;i<(1<<m);i++) //【预处理】初始化第一行(行数编号是0)
    if(!(i&a[0] || (i&(i<<1)) || (i&(i<<2)))) //判断‘行放置情况’有没有冲突
      f[0][i][0]=sums[i]; //第一行要特殊判定,相当于赋初始值

  for(int j=0;j<(1<<m);j++) //【预处理】初始化第二行(行数编号是1)
    for(int k=0;k<(1<<m);k++) //j是上一行,k是这一行
      if(!(j&k || j&a[0] || k&a[1] || (j&(j<<1)) || (j&(j<<2)) 
          || (k&(k<<1)) || (k&(k<<2)) ) )
        f[1][j][k]=sums[k]+sums[j]; //第二行要特殊判定(因为没有2-2=0行)

  for(int i=2;i<n;i++) //枚举行数
    for(int j=0;j<(1<<m);j++){ //【预处理】“相邻两个1的距离不小于3”的所有M位二进制数
      if(j&a[i-1] || (j&(j<<1)) || (j&(j<<2))) continue;  //‘行放置状态’冲突
      for(int k=0;k<(1<<m);k++){ //j是上一行(i-1),k是这一行(i)
        if(k&a[i] || j&k || (k&(k<<1)) || (k&(k<<2))) continue; //特判
        for(int FL=0;FL<(1<<m);FL++){ //FL是上上一行
          if(FL&j || FL&k || FL&a[i-2] || (FL&(FL<<1)) || (FL&(FL<<2))) continue;   
          f[i%3][j][k]=max(f[i%3][j][k],f[(i-1)%3][FL][j]+sums[k]); //转移:max放置数
        }
      }
    } 

  for(int j=0;j<(1<<m);j++) for(int k=0;k<(1<<m);k++) anss=max(anss,f[(n-1)%3][j][k]);
  cout<<anss<<endl; return 0; //倒数两行可以是任意状态(可行才会有值),求出最终的max放置个数

}

后来自己重新打了一遍,果然还是把循环里的 j++ 写成了 i++ ...

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;

//用每个m位二进制数表示一行的状态:
//每个二进制数的第k位为1表示在第k列上放置部队。

/*【p2704】炮兵阵地
一个N*M的地图,每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示)。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队)。
每个部队能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
两支炮兵部队之间不能互相攻击,即任何炮兵部队都不在其他部队的攻击范围内。
在整个地图区域内最多能够摆放多少我军的炮兵部队。*/

int sums[(1<<10)]; //sums[x]表示x的二进制表示中1的个数

int f[3][(1<<11)][(1<<11)]={0}; //【滚动数组】记录前两行的状态即可,即(%3=)0,1,2 。

int n,m,a[109],anss=0; //a[109]记录每行的初始可行状态(是二进制数转化为十进制的数)

void reads(int &x){ int fx=1;x=0;char s=getchar();
  while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
  while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=fx; }

int get_cnt(int S){ //当前状态 S 里面包含几个 1
  int tot=0; while(S){if(S&1) tot++; S>>=1;} return tot;
} //【坑点!!】这个一定要写成函数,要不然会TLE...

int main(){
  reads(n); reads(m); char ss;

  for(int i=0;i<n;i++) for(int j=0;j<m;j++) cin>>ss,a[i]<<=1,a[i]+=(ss=='H'?1:0);
  
  for(int i=0;i<(1<<m);i++) sums[i]=get_cnt(i); //先处理出sums

  for(int i=0;i<(1<<m);i++) for(int j=0;j<(1<<m);j++)
    if( ! ( (i&a[1]) || (j&a[0]) || (i&j) || (i&(i<<1)) //只需预处理第二行
      || (i&(i<<2)) || (j&(j<<1)) || (j&(j<<2)) ) ) f[1][i][j]=sums[j]+sums[i];
  
  for(int i=2;i<n;i++) //注意这里要从i=2开始
    for(int j=0;j<(1<<m);j++){ //上一行
      if(j&a[i-1]||(j&(j<<1))||(j&(j<<2))) continue;
      for(int k=0;k<(1<<m);k++){ if(j&k) continue;
        if(k&a[i]||(k&(k<<1))||(k&(k<<2))) continue;
        for(int LS=0;LS<(1<<m);LS++){ if((j&LS)||(k&LS)) continue;
          if(LS&a[i-2]||(LS&(LS<<1))||(LS&(LS<<2))) continue;
          f[i%3][k][j]=max(f[i%3][k][j],f[(i-1)%3][j][LS]+sums[k]); }
      }
  }

  for(int j=0;j<(1<<m);j++) for(int k=0;k<(1<<m);k++) anss=max(anss,f[(n-1)%3][j][k]);
  cout<<anss<<endl; return 0; //倒数两行可以是任意状态(可行才会有值),求出最终的max放置个数

}
【p2704】炮兵阵地 //重构之后的代码

T2:【p1896】互不侵犯

  • 国王能攻击到它上下左右、以及左上左下右上右下八个方向上一个格子。
  • 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。

f[i][j][s]表示考虑到了第i行,已经放置了j个国王,上一行的放置情况为s。

枚举这一行的放置情况t。要满足:t是合法的,并且可以与s作相邻行。

转移方程:f[i][j+count(t)][t]+=f[i-1][j][s]; //【方案数DP】+【状压DP】

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【洛谷p1896】互不侵犯
国王能攻击到它上下左右、以及左上左下右上右下八个方向上一个格子。
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。*/

//【方案数DP】+【状压DP】
//f[i][j][s]表示考虑到了第i行,已经放置了j个国王,上一行的放置情况为s。
//枚举这一行的放置情况t。要满足:t是合法的,并且可以与s作相邻行。
//转移方程:f[i][j+count(t)][t]+=f[i-1][j][s];

//count(t)表示t的二进制表示下1的数量,就是这一行的国王数。

void reads(int &x){ int fx=1;x=0;char s=getchar();
  while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
  while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=fx; }

int n,m,cnt,MAX; ll f[20][400][1000];

int can[1000],sums[2000]; //行能到达的状态,每个状态的国王数

int get_cnt(int x){ int ret=0; while(x) ret+=(x&1),x>>=1; return sums[cnt]=ret; }

int main(){
    int l,x,y; ll ans=0; reads(n),reads(m); MAX=(1<<n)-1; //二进制状态总数
    for(int i=0;i<=MAX;i++) //预处理可行的‘行状态’,初始化dp数组的第一行
        if(!(i&(i<<1))) can[++cnt]=i,f[1][get_cnt(i)][cnt]=1; 
    for(int i=2;i<=n;i++) for(int j=1;j<=cnt;j++){ x=can[j]; //此行的状态
        for(int k=1;k<=cnt;k++){  y=can[k]; //下一行的状态
            if((x&y)||(x&(y<<1))||(x&(y>>1))) continue; //不能相邻
            for(int l=0;l<=m;l++) f[i][l+sums[j]][j]+=f[i-1][l][k]; //枚举之前已经放的个数
        } //转移:f[i][count(t)+上行总国王数][状态]+=f[i-1][上行总国王数][上行状态];
    } for(int i=1;i<=cnt;i++) ans+=f[n][m][i]; printf("%lld\n",ans);
}
【p1896】互不侵犯

T3:【p3052】摩天大楼的奶牛

  • 给出n个物品,体积分别为w[i],现把其分成若干组,
  • 要求每组总体积<=W,问最小分组。(n<=18)。

f[i][j]表示已经分了i组,n个物品的选择状态是j时,当前组中的min体积。

如果存在d[i][j]这种方案,枚举不属于状态j的物品k,转移到d[i/i+1][j&(1<<k)]。

从前往后搜索,得到第一个存在j=2^n-1的方案即可。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;

/*【p3052】摩天大楼的奶牛
给出n个物品,体积分别为w[i],现把其分成若干组,
要求每组总体积<=W,问最小分组。(n<=18)。 */

/*【分析】状压DP
f[i][j]表示已经分了i组,n个物品的选择状态是j时,当前组中的min体积。
如果存在d[i][j]这种方案,枚举不属于状态j的物品k,转移到d[i/i+1][j&(1<<k)]。
从前往后搜索,得到第一个存在j=2^n-1的方案即可。 */

void reads(int &x){ int fx=1;x=0;char s=getchar();
  while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
  while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=fx; }

int f[20][(1<<20)]; int n,m,w[20];

int main(){
  reads(n); reads(m); for(int i=0;i<n;i++) reads(w[i]);
  for(int i=0;i<=n;i++) //分组数
    for(int j=0;j<=(1<<n)-1;j++) //每个状态
      f[i][j]=0x3f3f3f3f; //初始化为+∞
  for(int i=0;i<=n;i++) f[1][1<<i]=w[i]; //边界:第1组放任意一个物品
  for(int i=0;i<=n;i++) //组数从0~n
    for(int j=0;j<=(1<<n)-1;j++)
      if(f[i][j]!=0x3f3f3f3f) //如果该状态存在【存在性】
        for(int k=0;k<n;k++){ //枚举不属于状态j的物品k
          if((j&(1<<k))!=0) continue; //k属于j,舍去  ↓↓↓判断是否更新f[i][j|(1<<k)]
          if(f[i][j]+w[k]<=m) f[i][j|(1<<k)]=min(f[i][j|(1<<k)],f[i][j]+w[k]);
          else f[i+1][j|1<<k]=w[k]; //上一组存不下,要多存一组
        } 
  for(int i=0;i<=n;i++) //组数从小到大
    if(f[i][(1<<n)-1]!=0x3f3f3f3f) //只要有满足全选的状态的方案
     { printf("%d\n",i); break; } //输出组数并退出
}
【p3052】摩天大楼的奶牛

T4:【p3622】动物园

  • 每个小朋友站在大围栏圈的外面,可以看到连续的 5 个围栏。
  • 你得到了所有小朋友喜欢和害怕的动物信息。
  • 当下面两处情况之一发生时,小朋友就会高兴:
  •     1.至少有一个他害怕的动物被移走
  •     2.至少有一个他喜欢的动物没被移走
  • 输出一个数,表示最多可以让多少个小朋友高兴。

考虑到不同的小朋友看见的围栏范围可能相同,要预处理num[pos][s]:

表示从第pos个开始的五个围栏移走状态为s(全满则为15=2^4-1)时,满意的人数。

f[i][s]表示‘枚举到第i个围栏,且[i,i+5]的围栏移走状态为s’时的最多满意人数。

则f[i][s]可以由第i-1个围栏移走和不移走两种状态转移得来:

  • f[i][s]=max⁡(f[i−1][(s&15)<<1],f[i−1][(s&15)<<1∣1])+num[i][s];

注意:在dp之前先枚举前五个的状态state。避免环形问题。

那么此时必须满足s=state才是有效状态,更新答案。//【状压DP】【环形问题】

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;

/*【p3622】动物园
每个小朋友站在大围栏圈的外面,可以看到连续的 5 个围栏。
你得到了所有小朋友喜欢和害怕的动物信息。
当下面两处情况之一发生时,小朋友就会高兴:
    1.至少有一个他害怕的动物被移走
    2.至少有一个他喜欢的动物没被移走
输出一个数,表示最多可以让多少个小朋友高兴。 */

/* 考虑到不同的小朋友看见的围栏范围可能相同,要预处理num[pos][s]:
表示从第pos个开始的五个围栏移走状态为s(全满则为15=2^4-1)时,满意的人数。 
f[i][s]表示‘枚举到第i个围栏,且[i,i+5]的围栏移走状态为s’时的最多满意人数。
则f[i][s]可以由第i-1个围栏移走和不移走两种状态转移得来:
    f[i][s]=max⁡(f[i−1][(s&15)<<1],f[i−1][(s&15)<<1∣1])+num[i][s];
注意:在dp之前先枚举前五个的状态state。避免环形问题。
那么此时必须满足s=state才是有效状态,更新答案。//【状压DP】【环形问题】 */

void reads(int &x){ int fx=1;x=0;char s=getchar();
  while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();}
  while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=fx; }

const int N=50019; int n,m,ans,f[N][40],num[N][40];

int main(){
    reads(n),reads(m);
    
    for(int i=1,E,a,b,t;i<=m;i++){
        reads(E),reads(a),reads(b);
        int l=0,d=0; //分别记录不喜欢的和喜欢的对应的状态
        for(int j=1;j<=a;j++) //不喜欢的,相应位置上为1
          reads(t),t=(t-E+n)%n,l|=1<<t;
        for(int j=1;j<=b;j++) //喜欢的,相应位置上为1
          reads(t),t=(t-E+n)%n,d|=1<<t;
        for(int j=0;j<32;j++) //num表示‘从E开始的5个围栏移走状态为s时,满意的人数’
            if((j&l)||(~j&d)) num[E][j]++; //j为要选择移走的状态
    }

    for(int i=0;i<32;i++){
        memset(f[0],128,sizeof(f[0])),f[0][i]=0; //初始-inf,设置dp起点
        for(int j=1;j<=n;j++) //枚举围栏数
          for(int s=0;s<32;s++) //枚举移走状态,从上一位置移走和不移走两种状态转移
            f[j][s]=max(f[j-1][(s&15)<<1],f[j-1][(s&15)<<1|1])+num[j][s];
        if(ans<f[n][i]) ans=f[n][i]; //更新ans
    }
    
    printf("%d\n",ans); return 0; //输出ans:最多的满意人数
}
【p3622】动物园

——时间划过风的轨迹,那个少年,还在等你。

猜你喜欢

转载自www.cnblogs.com/FloraLOVERyuuji/p/10601568.html