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放置个数 }
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); }
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; } //输出组数并退出 }
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:最多的满意人数 }
——时间划过风的轨迹,那个少年,还在等你。