状压dp:用二进制压缩集合状态进行转移。
TSP问题:
91. 最短Hamilton路径
给定一张完全图,求从0出发,周游所有点的最短曼哈顿距离,
定义dp【i】【s】为从0点出发,走到 i 为终点,经过的集合为S的最短距离,
那么初始化dp【0】【1】=0;直接转移,那么答案为dp【n-1】【1<<n -1】
复杂度O(n*n*2^n)
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=1e6+500; int n,m; int gp[25][25],dp[25][N]; int main(){ scanf("%d",&n); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ scanf("%d",&gp[i][j]); } } memset(dp,inf,sizeof dp); // for(int i=0;i<n;i++)dp[i][1<<i]=0; dp[0][1]=0; for(int S=1;S<(1<<n);S++){ for(int u=0;u<n;u++){ if(!(S&(1<<u)))continue; for(int v=0;v<n;v++){ if(v==u)continue; if(S&(1<<v))dp[u][S]=min(dp[u][S],dp[v][S^(1<<u)]+gp[v][u]); } } } cout<<dp[n-1][(1<<n)-1]<<endl; // system("pause"); return 0; }
Travelling
一张完全图,每个点可以走0,1,2次,求周游所有点的最小曼哈顿距离,那么用三进制压缩状态,定义dp【i】【S】为 i 为起点,经过的集合为S的最短距离,
那么答案为所有的结果取小。预处理每一个状态对应每一位的情况,直接转移即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int inf=0x3f3f3f3f; 4 const int N=6e4+500; 5 int n,m; 6 int gp[20][20]; 7 int bit[]={0,1,3,9,27,81,243,729,2187,6561,19683,59049}; 8 int path[N][20],dp[20][N]; 9 void init(){ 10 memset(gp,inf,sizeof gp); 11 memset(dp,inf,sizeof dp); 12 for(int i=0;i<N;i++){ 13 int t=i; 14 for(int j=1;j<=n;j++){ 15 path[i][j]=t%3;t/=3; 16 } 17 } 18 19 } 20 int main(){ 21 while(~scanf("%d %d",&n,&m)){ 22 init(); 23 while(m--){ 24 int u,v,w;scanf("%d %d %d",&u,&v,&w); 25 if(w<gp[u][v])gp[u][v]=gp[v][u]=w; 26 } 27 int ans=inf; 28 for(int i=0;i<=n;i++)dp[i][ bit[i] ]=0; 29 for(int j=0;j<=59049;j++){ 30 bool flag=1; 31 for(int i=1;i<=n;i++){ 32 33 if(path[j][i]==0){flag=0;continue;} 34 for(int k=1;k<=n;k++){ 35 if(k==i)continue; 36 int l=j-bit[i]; 37 dp[i][j]=min(dp[i][j],dp[k][l]+gp[k][i]); 38 } 39 40 } 41 42 if(flag){ 43 for(int i=1;i<=n;i++)ans=min(ans,dp[i][j]); 44 } 45 } 46 // cout<<"ans :"; 47 printf("%d ",(ans==inf?-1:ans)); 48 49 } 50 // system("pause"); 51 return 0; 52 }
Victor and World
从1开始,周游所有点回到1,求最下距离,
注意边很多,点很少,先floyd跑最短路,定义dp【i】【s】为1为起点,i为终点,经过的集合为s的最短距离,
那么答案即为 :dp[0][(1<<n)-1] dp[i][(1<<n)-1]+gp[i][0] 取小,
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int inf=0x3f3f3f3f; 4 const int N=6e5+500; 5 int n,m; 6 int gp[20][20],dp[20][N]; 7 void floyd(){ 8 for(int k=0;k<n;k++){ 9 for(int i=0;i<n;i++){ 10 for(int j=0;j<n;j++){ 11 gp[i][j]=min(gp[i][j],gp[i][k]+gp[k][j]); 12 } 13 } 14 } 15 } 16 void init(){ 17 memset(gp,inf,sizeof gp); 18 memset(dp,inf,sizeof dp); 19 for(int i=0;i<n;i++)gp[i][i]=0; 20 } 21 int main(){ 22 int t;scanf("%d",&t); 23 while(t--){ 24 scanf("%d %d",&n,&m); 25 init(); 26 while(m--){ 27 int u,v,w;scanf("%d %d %d",&u,&v,&w); 28 u--;v--; 29 if(w<gp[u][v])gp[u][v]=gp[v][u]=w; 30 } 31 floyd(); 32 dp[0][1]=0; 33 for(int S=1;S<(1<<n);S++){ 34 for(int u=0;u<n;u++){ 35 if(!(S&(1<<u)))continue; 36 for(int v=0;v<n;v++){ 37 if(v==u)continue; 38 if(S&(1<<v))dp[u][S]=min(dp[u][S],dp[v][S^(1<<u)]+gp[v][u]); 39 } 40 } 41 } 42 int ans=dp[0][(1<<n)-1]; 43 for(int i=1;i<n;i++)ans=min(ans,dp[i][(1<<n)-1]+gp[i][0]); 44 printf("%d ",ans); 45 } 46 // system("pause"); 47 return 0; 48 }
Corn Fields
一张网格图,种玉米,1之间不能相邻。问有多少种方案。
定义dp【i】【s】为i行状态为s的方案数,那么只要和地图,前一行的状态不冲突即可。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int inf=0x3f3f3f3f; const int N=6e5+500; int n,m; int a[20][20],dp[20][N],num[20]; // bool fit(int x,int k){ // } const int MOD=100000000; int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=n;i++){ num[i]=0; for(int j=1;j<=m;j++){ int x;scanf("%d",&x); if(x==0)num[i]+=1<<(m-j); } } memset(dp,0,sizeof dp); for(int s=0;s<(1<<m);s++){ if(!(s&s<<1)&& !(s&num[1]) )dp[1][s]=1; } for(int i=2;i<=n;i++){ for(int s=0;s<(1<<m);s++){ if((s&s<<1)||s&num[i])continue; for(int t=0;t<(1<<m);t++){ if(!(t&t<<1) && !(t&num[i-1]) && !(s&t))dp[i][s]=(dp[i][s]+dp[i-1][t])%MOD; } } } int ans=0; for(int s=0;s<(1<<m);s++)if(!(s&s<<1)&&!(s&num[n]))ans=(ans+dp[n][s])%MOD; ans%=MOD; cout<<ans<<endl; // system("pause"); return 0; }
郑厂长系列故事——排兵布阵
一个地图,每个士兵不能曼哈顿距离为2的地方不能有士兵,求能放的最大士兵数。
本来设dp【i】【s】为枚举到 i 行,状态为 s 的方案数,但这样转移实际上还要考虑 i - 2 行的状态,判断是否冲突。
不妨设 dp 【 i 】【 j 】【 k 】为 枚举到 i 行,i 状态为 j i - 1行状态为 k 的方案数,直接转移取大即可。
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=6e3+500; int n,m; int dp[120][200][200],a[120][120]; vector<int>v; void init(){ v.clear(); for(int s=0;s<(1<<m);s++){ if(!(s&(s<<2))&&!(s&(s>>2)))v.push_back(s); } } int count(int i,int x){ int sum=0; for(int j=m;j>=1;j--){ if(x&1)sum+=a[i][j]; x>>=1; } return sum; } int main(){ while(~scanf("%d %d",&n,&m)){ init(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&a[i][j]); } } memset(dp,0,sizeof dp); int ans=0; for(int i=1;i<=n;i++){ for(int j=0;j<v.size();j++){ for(int k=0;k<v.size();k++){ if(i==1){ dp[i][j][k]=count(i,v[j]); } else { if(v[j]&(v[k]>>1)||v[j]&(v[k]<<1))continue; int tmp=0; for(int p=0;p<v.size();p++){ if(v[k]&(v[p]>>1)||v[k]&(v[p]<<1))continue; if(v[p]&v[j])continue; tmp=max(tmp,dp[i-1][k][p]); } dp[i][j][k]=tmp+count(i,v[j]); } ans=max(ans,dp[i][j][k]); } } } cout<<ans<<endl; } // system("pause"); return 0; }
Mondriaan's Dream
设dp[ i ] [ j ]为推到 i 行,状态为j,1表示是竖着放的上半块,0表示其他,那么只要保证1下面都是0,而且j | k不出现连续的奇数0,因为横着放达不到,直接递推即可。
#include<iostream> #include<cstring> using namespace std; const int inf=0x3f3f3f3f; const int N=1e5+500; typedef long long ll; ll dp[20][1<<12]; bool ins[1<<12]; int main(){ int n,m; while(cin>>n>>m&&n){ memset(dp,0,sizeof dp); memset(ins,1,sizeof ins); for(int s=0;s<(1<<m);s++){ int cnt=0,odd=0; for(int j=0;j<m;j++){ if((s>>j)&1)odd=odd|cnt,cnt=0; else cnt^=1; } odd=odd|cnt; if(odd)ins[s]=0; } dp[0][0]=1; for(int i=1;i<=n;i++){ for(int s=0;s<(1<<m);s++){ for(int k=0;k<(1<<m);k++){ if(!(s&k)&&ins[s|k])dp[i][s]+=dp[i-1][k]; } } } // cout<<"test :"; cout<<dp[n][0]<<endl; } // system("pause"); return 0; }
变滚动
#include<iostream> #include<cstring> using namespace std; const int inf=0x3f3f3f3f; const int N=1e5+500; typedef long long ll; ll dp[2][1<<12]; bool ins[1<<12]; int main(){ int n,m; while(cin>>n>>m&&n){ memset(dp,0,sizeof dp); memset(ins,1,sizeof ins); for(int s=0;s<(1<<m);s++){ int cnt=0,odd=0; for(int j=0;j<m;j++){ if((s>>j)&1)odd=odd|cnt,cnt=0; else cnt^=1; } odd=odd|cnt; if(odd)ins[s]=0; } int old=0,now=1; dp[old][0]=1; for(int i=1;i<=n;i++){ for(int s=0;s<(1<<m);s++){ dp[now][s]=0; for(int k=0;k<(1<<m);k++){ if(!(s&k)&&ins[s|k])dp[now][s]+=dp[old][k]; } } for(int j=0;j<(1<<m);j++)dp[old][j]=dp[now][j]; } cout<<dp[1][0]<<endl; } // system("pause"); return 0; }
炮兵阵地
#include<iostream> #include<cstring> #include<vector> // #include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=1e5+500; typedef long long ll; int dp[120][120][120]; char mp[120][120]; vector<int>v; int n,m; int get(int x,int y){ int cnt=0; for(int j=1;j<=m;j++){ if(mp[x][j]=='P'&&(y>>(m-j))&1)cnt++; } return cnt; } int main(){ cin>>n>>m; for(int s=0;s<(1<<m);s++)if( !( (s<<2)&s||(s>>2)&s|| (s<<1)&s||(s>>1)&s) )v.push_back(s); memset(dp,0,sizeof dp); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>mp[i][j]; } } int ans=0; for(int i=1;i<=n;i++){ for(int j=0;j<v.size();j++){ for(int k=0;k<v.size();k++){ if(v[j]&v[k])continue; for(int s=0;s<v.size();s++){ if(v[j]&v[s]||v[k]&v[s])continue; dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][s]+get(i,v[j])); } ans=max(ans,dp[i][j][k]); } } } cout<<ans<<endl; // system("pause"); return 0; } // 5 4 // PHPP // PPHH // PPPP // PHPP // PHHP