状压Dp初学
第一次学习状压DP,感觉就是Dp的优化版本,把存不下但是好表示的状态用数字表示一下,
对所有的约束条件也用数表示,例如
T1:
问题描述≤20)
在n*n的方格棋盘上放置n个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。
输入格式
一行,若干个1到20之间的整数。
输出格式
若干行,对于输入的每个整数,输出其方案数。
样例输入
3 2
样例输出
6
2
限制与约定
时间限制:1s
空间限制:128MB
数学方法:A(n,n)
状压DP法:
设f[i]表示哪些行放置旗子的方案数,不考虑顺序,
所以
f[0]=1;f[001]=f[000];f[011]=f[001]+f[010];以此类推,表示的是,在第j行多放一个
是从 不放这行的 方案数 转移过来的
Code:
#include<cstdio> #define LL long long using namespace std; const int maxn=(1<<21)-1; LL f[maxn]; int lowbit[maxn]; int main() { int n; for(int i=1;i<=maxn;i++)lowbit[i]=i&(-i);//预处理实际上意义不大 f[0]=1;//注意边界条件 for(int i=1;i<=maxn;i++){//使用2进制枚举,复杂度O(n^2) int j=i; while(j){ f[i]+=f[i-lowbit[j]];//每次去掉一位 j-=lowbit[j]; } } while(scanf("%d",&n)==1){ printf("%lld\n",f[(1<<n)-1]); } return 0; }
496. 棋盘问题2
问题描述 n×n(n≤20)
在n*n的方格棋盘上放置n 个车(可以攻击所在行、列),某些格子不能放,求使它们不能互相攻击的方案总数。
输入格式
给你一个n和m,分别表示棋盘的大小,不能放的格子总数。接下来是m行坐标,表示不能放置的位子。
输出格式
符合条件的方案总数。
输入样例1
3 2
1 2
2 3
输出样例1
3
输入样例2
4 1
1 1
输出样例2
18
限制与约定
时间限制:1s
空间限制:128MB
多了一个限制条件,我们也把他压缩
#define LL long long #include<cstdio> using namespace std; int n,m,a,b; LL f[1<<21]; bool ban[1<<21][21]; inline int getone(int n){ int ans=0; while(n){ if(n&1)ans++; n>>=1; } return ans; } int main() { scanf("%d%d",&n,&m); while(m--){ scanf("%d%d",&a,&b); ban[1<<(a-1)][b]=1; } f[0]=1; for(int i=1;i< 1<<n;i++){ int j=i,cnt=getone(i);//只有该行不能转移方案,其他行可以 while(j){ int lowbit=j&-j; if( !ban[lowbit][cnt] )f[i]+=f[i-lowbit]; j-=lowbit; } } printf("%lld\n",f[(1<<n)-1]); return 0; }
or:
更加节省空间:
#define LL long long #include<cstdio> using namespace std; int n,m,a,b; LL f[1<<21]; LL ban[21]; inline int getone(int n){ int ans=0; while(n){ if(n&1)ans++; n>>=1; } return ans; } int main() { scanf("%d%d",&n,&m); while(m--){ scanf("%d%d",&a,&b); ban[b]|=1<<(a-1); } f[0]=1; for(int i=1;i< 1<<n;i++){ int j=i,cnt=getone(i); while(j){ int lowbit=j&-j; if(! (ban[cnt]&lowbit) )f[i]+=f[i-lowbit]; j-=lowbit; } } printf("%lld\n",f[(1<<n)-1]); return 0; }
497. 棋盘问题3(good question)
问题描述
给出一个n×m
的棋盘(n、m≤80,n×m≤80
),要在棋盘上放k(k≤20)个棋子, 使得任意两个棋子不相邻(相邻指上下左右四个方向)。
求可以放置的总的方案数。
输入格式
一行三个整数n,m,k,表示棋盘大小为n行m列,棋盘上放置k个棋子。
输出格式
一个整数表示方案数。
输入样例1
3 3 2
输出样例1
24
输入样例2
2 2 2
输出样例2
2
输入样例3
20 1 2
输出样例3
171
限制与约定
时间限制:1s
空间限制:128MB
分析:
本题第一眼看起来像f[i][j][k][b]的Dp,但是没敢写因为巨麻烦,转移方程非常繁琐,
但是状压DP直接压得话,肯定会爆空间得
那我们怎么压,压什么
我们考虑优化,nm<=80,算数原理,n和m之间一定有一个<10的数,这就可以用2进制压缩了
(并不一定压k,压不了)
因为每一行都有关系,而且该行符合条件的状态长得都一样,
所以我们预处理出来一个存2合法状态的数组,
节省空间时间,
我们设计状态,
肯定有一维是行数,然后肯定有一维 是状压的二进制数,然后还应该有一维是当前用的旗子数,
状压什么?
压我们用起来最方便的,该行的排列,状态数<=2^9 并且能和上一行状态转移
于是思路就出来了
Code is as follow
AC:
#include<cstdio> #include<algorithm> #include<cstring> #define LL long long using namespace std; int n,m,k,temp; LL f[100][100][21],s[100],one[100];//行与行之间是相互影响的 //f[i][j][k]表示i行,j列排列情况,k个旗子的方案数 void dfs(int dep,int num,int cnt){ if(dep==n+1){ s[++temp]=num>>1;//一开始就空出来1位 one[temp]=cnt; return; } else{ if( ! (num& 1<<(dep-1)) )dfs(dep+1,num|(1<<dep),cnt+1); dfs(dep+1,num,cnt); } } int main() { scanf("%d%d%d",&n,&m,&k); if(n>m)swap(n,m);//n<=m -> n<10 dfs(1,0,0); f[0][temp][0]=1; for(int i=1;i<=m;i++){ for(int j=1;j<=temp;j++) for(int v=one[j];v<=k;v++) for(int p=1;p<=temp;p++){ if( !(s[j]&s[p]) )f[i][j][v]+=f[i-1][p][v-one[j]]; } } LL ans=0ll; for(int i=1;i<=temp;i++){ ans+=f[m][i][k]; } printf("%lld",ans); return 0; }
498. 棋盘问题4
问题描述
在n×n(n≤10)
的棋盘上放k 个国王(可攻击相邻的8 个格子),求使它们无法互相攻击的方案数。
输入格式
一行两个整数n,k,棋盘大小为n行n列,棋盘上放置k个棋子。
输出格式
一个整数表示方案数。
输入样例1
2 1
输出样例1
4
输入样例2
3 2
输出样例2
16
限制与约定
时间限制:1s
空间限制:128MB
没有任何区别,只是判断时加了一个条件:
#include<cstdio> #include<algorithm> #include<cstring> #define LL long long using namespace std; int n,k,temp; LL f[15][1025][105],s[1025],one[1025];//行与行之间是相互影响的 //f[i][j][k]表示i行,j列排列情况,k个旗子的方案数 void dfs(int dep,int num,int cnt){ if(dep==n+1){ s[++temp]=num>>1;//一开始就空出来1位 one[temp]=cnt; return; } else{ if( ! (num& 1<<(dep-1)) )dfs(dep+1,num|(1<<dep),cnt+1); dfs(dep+1,num,cnt); } } int main() { scanf("%d%d",&n,&k); //if(n>m)swap(n,m);//n<=m -> n<10 dfs(1,0,0); f[0][temp][0]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=temp;j++) for(int v=one[j];v<=k;v++) for(int p=1;p<=temp;p++){ if( !(s[j]&s[p]) && !((s[j]<<1)&s[p]) && !((s[j]>>1)&s[p]) )
f[i][j][v]+=f[i-1][p][v-one[j]]; } } LL ans=0ll; for(int i=1;i<=temp;i++){ ans+=f[n][i][k]; } printf("%lld",ans); return 0; }
500. 棋盘问题6
问题描述
给出n×m
(1≤n、m≤11)的方格棋盘,用1×2
的长方形骨牌不重叠地覆盖这个棋盘,求覆盖满的方案数。
输入格式
第一行两个整数n,m,表示棋盘大小为n行m列。
输出格式
一个整数表示方案数。
输入样例
2 4
输出样例
5
限制与约定
时间限制:1s
空间限制:128MB
这个问题一开始没想到,但是经过分析之后发现确实可以状压
我们设每一行的状态为(2)0001001000...
其中0表示使用横向牌 或者 被上一次的竖向牌覆盖
(或者表示将被下一次的竖向牌覆盖,都一样的)
1表示使用放置竖向牌的开头
然后我们设计状态转移方程:
f[ i ][ ( 2 ) j ]=sum(f[ i-1 ][ ( 2 ) k ]) ,j|k==0 and j &k合法
然后计算一下复杂度,可以接受
Code:
#include<cstdio> #include<algorithm> #define LL long long using namespace std; int n,m,situ[1<<12],cnt; LL f[15][1<<12]; //f[i][(2)0101010101]表示第i行 0->一层,1->两层,注意两个1之间至少2个0 bool True[1<<12]; void dfs_True(int dep,int ans){ if(dep==n+1){ True[ans]=1; return; } if(dep>n)return; dfs_True(dep+1,ans|1<<(dep-1)); dfs_True(dep+2,ans); } int main(){ scanf("%d%d",&n,&m); if(n>m)swap(n,m); dfs(1,0,-10); dfs_True(1,0); f[0][0]=1; for(int i=1;i<=m;i++){ for(int j=0;j<(1<<n);j++){//注意二进制枚举 不能预处理,会丢掉情况 for(int k=0;k<(1<<n);k++){ if( !(j&k) && True[ j | k ]) f[i][ j ]+=f[i-1][ k ]; } } } printf("%lld",f[m][0]);//注意最后的答案长什么样子 return 0; }
!!!503. 炮兵阵地(noi2001)
问题描述
司令部的将军们打算在N
的网格地图上部署他们的炮兵部队。一个N
的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入文件
文件的第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。
N≤100;M≤10。
输出文件
文件仅在第一行包含一个整数K,表示最多能摆放的炮兵部队的数量。
输入样例
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例
6
限制与约定
时间限制:1s
空间限制:128MB
省选题???
我们可以发现这可以状压,但是状态量极大,而且转移起来涉及前两行的状态
那我们考虑优化
首先,要想满足限制,该行就首先要符合限制,我们预处理出合法状态的数组,因为很难初始化
第0行也就是边界,所以我们直接在dfs中初始化
然后,考虑是否可以枚举两层,前两行的状态,然后捏在一起给当前行,发现
理论上可以,但是,上一行的状态包括了上两行的状态,所以我们还得容斥去重。。。
非常繁琐,再根据这题只有 120/每层for 的复杂度,我们大胆的将状态 拓展到3维
f[i][(2)j][(2)k]表示第i行,该行是 j 的状态,上一行是 k 的状态 的最优解
Code:
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define LL long long using namespace std; int n,m,c[200],s[200],ban[105],temp; int f[105][200][200]; void dfs(int dep,int ans,int cnt){ if(dep>m){ s[++temp]=ans; c[temp]=cnt; if(!(ans&ban[1]))f[1][temp][1]=cnt;//直接初始化f[1] return; } dfs(dep+1,ans,cnt); dfs(dep+3,ans|1<<(dep-1),cnt+1); } int main(){ scanf("%d%d",&n,&m); memset(f,0xf1,sizeof(f));//Dp初值 dfs(1,0,0); char x[15]; for(int i=1;i<=n;i++){ scanf("%s",x+1); for(int j=1;j<=m;j++){ if(x[j]=='H'){ ban[i]|=1<<(j-1);// 限制条件也状压 } } } int ans=0; for(int i=2;i<=n;i++){ for(int j=1;j<=temp;j++){ if(s[j]&ban[i])continue; for(int k=1;k<=temp;k++){//上一行 if(s[j]&s[k] || s[k]&ban[i-1])continue; for(int p=1;p<=temp;p++){//上2行 if(s[p]&ban[i-2] || s[p]&s[j] || s[p]&s[k])continue; f[i][j][k]=max(f[i-1][k][p]+c[j],f[i][j][k]); if(i==n)ans=max(ans,f[i][j][k]); // 便于统计答案,减少点时间复杂度 } } } } printf("%d",ans); return 0; }
题目描述
在一个N行M列的棋盘上,放若干个炮(可以不放),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。
在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子
输入输出格式
输入格式:
一行包含两个整数N,M,之间由一个空格隔开。
输出格式:
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
输入输出样例
输入样例#1:
1 3
输出样例#1:
7
说明
样例说明
除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有2*2*2-1=7种方案。
数据范围
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
???
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int INF =1e7; int ans,n,f[16][2<<16],log[2<<16]; //f[i][(2)j]表示当前走到第i个城市,已经过 j 城市的最短路径 int main(){ scanf("%d",&n); memset(f,0x3f,sizeof(f)); ans=INF; //printf("%d\n",(1<<n)-1); for(int i=0;i<=n;i++){//log(1)=1; log[1<<i]=i+1; } int x; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%d",&x); if(x)f[j][ 1<<(j-1) | 1<<(i-1) ]= x; } } for(int i=2;i<=n;i++){ for(int j=1;j<(1<<n);j+=2){ if(j & 1<<(i-1))continue; int target=j|1<<(i-1); int k=j-1; while(k){ int lowbit=k&(-k); int p=log[lowbit]; f[i][target]=min(f[i][target],f[p][j]+f[i][lowbit|1<<(i-1)]); k-=lowbit; } //printf("%d %d %d\n",i,target,f[i][target]); } ans=min(ans,f[i][(1<<n)-1]+f[1][1|1<<(i-1)]); } printf("%d",ans); return 0; }
n个物品 每个物品m个属性,不同的排列,相邻两数差的绝对值的最小值最大,
#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<iostream> #include<cstdlib> #define LL long long using namespace std; template<typename T> inline void read(T &a){ a=0;bool b=0;char x=getchar(); while(x<'0'||'9'<x){ if(x=='-')b=1; x=getchar(); } while('0'<=x&&x<='9'){ a=(a<<1)+(a<<3)+x-'0'; x=getchar(); } if(b)a=-a; } char C_[50]; int TEMP; template<typename T> inline void write(T a){ if(a<0){ a=-a; putchar('-'); } do{ C_[++TEMP]=a%10+'0'; a/=10; }while(a); while(TEMP)putchar(C_[TEMP--]); } //最小值最大:二分 or DP(其实可以) const int maxn=17; const int maxm=1e4+5; const int INF=1e7; int ans,n,m,a[maxn+1][maxm],f[maxn][maxn][10+(1<<16)]; int g[20][20][2],flog[(1<<maxn)+10]; int main(){ //freopen("1.in","r",stdin); read(n);read(m); for(int i=0;i<n;i++)flog[1<<i]=i+1; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ read(a[i][j]); } } for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){ if(i==j)continue; g[i][j][0]=g[i][j][1]=INF; for(int k=1;k<=m;k++){ g[i][j][0]=min(g[i][j][0],abs(a[i][k]-a[j][k])); if(k<m)g[i][j][1]=min(g[i][j][1],abs(a[j][k]-a[i][k+1])); } f[i][j][(1<<(i-1)) | (1<<(j-1))]=g[i][j][0]; //printf("%d %d %d\n",i,j,f[i][j][1<<(i-1) | 1<<(j-1)]); } for(int s=1;s< (1<<n);s++){//枚举可选状态 /* for(int s=0;s< (1<<n);s++) 当n==2 时 会出错, 注意状压DP的答案更新 */ //printf("%d:\n",s); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(i==j || s&(1<<(i-1)) || s&(1<<(j-1)))continue; //printf("--------%d %d ",i,j); int target=s + (1<<(i-1)) + (1<<(j-1)); int k=s; while(k){ int lowbit=k&(-k); int pos=flog[lowbit]; f[i][j][target]=max(f[i][j][target],min(f[i][pos][target-(1<<(j-1))],f[pos][j][lowbit+(1<<(j-1))])); k-=lowbit; } //printf("%d\n",f[i][j][target]); if(target+1==(1<<n))ans=max(ans,min(f[i][j][target],g[i][j][1])); } } printf("%d",ans); return 0; }