状压dp
因为有道ac自动机+状压dp的题,故开此坑。
状压dp:状态压缩dp(元素数量通常不超过20),借助位运算将状态压缩。
空间复杂度:O(n*n)
上一行的状态为now,下一行的状态为prev,通过枚举上一行所有状态,来更新当前行、当前状态的最优解。
给定n*m矩阵,行列都不超过20,有些格子可选有些不可选,需选出最多格子使得格子之间不相邻(无公共边)
#include <iostream> #include <string.h> using namespace std; const int MAX_N=20; const int MAX_M=20; int state[MAX_N+1]; int dp[MAX_N+1][ 1 <<MAX_M]; bool not_intersect(int now,int prev) { return (now&prev)==0; } bool fit(int now,int flag) { return(now|flag)==flag; } bool ok(int x) { return(x&(x/2))==0; } int count(int now) { int s=0; while(now) { s+=(now&1); now>>=1; } return s; } int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { for(int j=0;j<m;j++) { int flag; cin>>flag; state[i]|=(1<<j)*flag; } } for(int i=1;i<=n;i++) { for(int j=0;j<(1<<m);j++) { if(!ok(j)||!fit(j,state[i])) {continue;} int cnt=count(j); for(int k=0;k<(1<<m);k++) { if(ok(k)&&fit(k,state[i-1])&¬_intersect(j,k)) { dp[i][j]=max(dp[i][j],dp[i-1][k]+cnt); } } } } int ans=0; for(int i=0;i<(1<<m);i++) { ans=max(ans,dp[n][i]); } cout<<ans<<endl; return 0; }
长度不超过16的字符串S,如果一个子序列是回文串,即可以移除它,求最少经过多少步能移除整个串。
序列回文?1:min(子集a次数,a的互补子集b次数),时间复杂度O(3n+n*2n)
枚举子集代码
for (int t = 1; t < (1 << n); t++) { // 枚举当前状态 dp[t] = IsPalindrome(t) ? 1 : inf; // 判断当前状态是否是回文,如果是回文则步骤数为 1 for(int i = t; i; i = (i - 1) & t) { // 枚举 t 的所有子集 dp[t] = min(dp[t], dp[i] + dp[t ^ i]); // 更新当前状态的解的最小值 } } printf("%d\n", dp[(1 << n) - 1]); // 输出最终答案
完整代码
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; char s[20]; int n,dp[1<<17]; bool ispa(int now) { string str=""; for(int i=0;i<n;i++) { if((now&(1<<i))==(1<<i)) { str+=s[i]; } } string str2=str; reverse(str2.begin(),str2.end()); return str2==str; } int main() { scanf("%s",s); n=strlen(s); for(int t=1;t<(1<<n);t++) { dp[t]=ispa(t)?1:inf; for(int i=t;i;i=(i-1)&t) { dp[t]=min(dp[t],dp[i]+dp[t^i]); } } int ans=dp[(1<<n)-1]; cout<<ans<<endl; return 0; }
↓一道差不多的题:https://www.luogu.org/problem/P2704
灌溉机器人,一个P只影响得到四个方向八个点,所以第r行用r-1行和r-2行来推,每一行状态推一下,同行不互斥的状态有60种,滚动数组
#include<bits/stdc++.h> using namespace std; const int maxn=100; const int maxm=10; int state[maxn+1],cnt,stateline[maxn+1],sum[maxn+1]; int dp[maxn+1][65][65]; bool fit(int now,int flag) { return (now|flag)==flag; } bool ok(int x) { return (x&(x/2))==0&&(x&(x/4))==0; } int count(int now) { int s=0; while(now) { s+=(now&1); now>>=1; } return s; } int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { for(int j=0;j<m;j++) { char flag; cin>>flag; int tmp=flag=='P'?1:0; state[i]|=(1<<j)*tmp; } } for(int i=0;i<(1<<m);i++) { if(ok(i)) { cnt++; stateline[cnt]=i; sum[cnt]=count(i); } } for(int i=1;i<=cnt;i++) { if(fit(stateline[i],state[1])) dp[1][1][i]=sum[i]; } for(int r=2;r<=n;r++) { for(int i=1;i<=cnt;i++) { if(!fit(stateline[i],state[r]))continue; for(int j=1;j<=cnt;j++) { if(stateline[i]&stateline[j])continue; for(int k=1;k<=cnt;k++) { if(stateline[i]&stateline[k])continue; dp[r][j][i]=max(dp[r][j][i],dp[r-1][k][j]+sum[i]); } } } } int ans=0; for(int i=1;i<=cnt;i++) { for(int j=1;j<=cnt;j++) { ans=max(ans,dp[n][i][j]); } } cout<<ans<<endl; return 0; }
n辆小车(物资)用一辆船运,每次运的车总重不能超过wei,每辆车重w[i],对船损坏值s[i],每次运输对船的损坏值为max 船上所有车损坏值,问运完所有车的min总共损坏值,枚举子集
#include<bits/stdc++.h> using namespace std; const int maxn=400; const int maxm=16+1; const int inf=0x3f3f3f3f; int s[maxm+1],w[maxm+1]; int dp[1<<maxm]; int wei,n; bool ok(int now) { int ans=0; for(int i=0;i<n;i++) { if((now&(1<<i))==(1<<i)) { ans+=w[i+1]; if(ans>wei) return false; } } return true; } int count(int now) { int ans=0; for(int i=0;i<n;i++) { if((now&(1<<i))==(1<<i)) ans=max(ans,s[i+1]); } return ans; } int main() { scanf("%d%d",&wei,&n); for(int i=1;i<=n;i++) { scanf("%d%d",&s[i],&w[i]); } for(int t=1;t<(1<<n);t++) { if(ok(t)){dp[t]=count(t);continue;} else dp[t]=inf; for(int i=t;i;i=(i-1)&t) { dp[t]=min(dp[t],dp[i]+dp[t^i]); } } int ans=dp[(1<<n)-1]; cout<<ans<<endl; return 0; }
n个点,有2n-1种状态(1到(1<<n)-1),给点填色,有线相连的点不能填同一个颜色,si表示状态i填色需要的最少颜色种类,求。枚举的时候枚举一下是否能用一个颜色就可以了。
如果mod=2^32,可以定义unsigned int,使其自然溢出。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long const int inf=0x3f3f3f3f; int n,dp[1<<16]; char ch[20][20]; unsigned int mod=1ll<<32; bool isone(int now) { bool ok[20]={false}; for(int i=0;i<n;i++) { if(now&(1<<i)) { ok[i+1]=true; } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(ch[i][j]=='1'&&ok[i]&&ok[j]) return false; } } return true; } int main() { cin>>n; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { cin>>ch[i][j]; } } unsigned int ans=0,c=233; for(int t=1;t<(1<<n);t++,c*=233) { if(isone(t))dp[t]=1; else { dp[t]=inf; for(int i=t;i;i=(i-1)&t) { dp[t]=min(dp[t],dp[i]+dp[t^i]); } } ans+=dp[t]*c; } cout<<ans<<endl; return 0; }
暂时完结...