状压dp初探
写了几道状压。。。然后就一直在颓废。。。
2064: 分裂
http://www.lydsy.com/JudgeOnline/problem.php?id=2064
初始的为正,最后的为负,假设我们能找到k组凑成0的话,答案就是n+m-2*k。于是状压。。其实我一点都不会。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define rep(i,l,r) for (int i=l;i<=r;i++) #define maxn 1<<22 using namespace std; int t,mm,f[maxn],sum[maxn],ans,n,m,a[50],b[50]; int main(){ scanf("%d",&n); rep(i,0,n-1) scanf("%d",&a[i]); scanf("%d",&m); rep(i,0,m-1) scanf("%d",&b[i]); rep(i,0,n-1) sum[1<<i]=a[i]; rep(i,n,n+m-1) sum[1<<i]=-b[i-n]; mm=1<<(n+m); rep(i,1,mm-1){ t=i&(-i); sum[i]=sum[t]+sum[i-t]; rep(j,0,m+n-1) if ((1<<j)&i) f[i]=max(f[i],f[i^(1<<j)]); if (!sum[i]) ++f[i]; } printf("%d\n",n+m-2*f[mm-1]); return 0; }
1725: [Usaco2006 Nov]Corn Fields牧场的安排
http://www.lydsy.com/JudgeOnline/problem.php?id=1725
f[i][j]表示第i行,情况是j的答案。于是裸状压。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define rep(i,l,r) for (int i=l;i<=r;i++) #define maxn 5000 #define mm 100000000 int y,sum,tot,ans,ff[20][maxn],f[20][maxn],g[20][maxn],d[maxn],map[20][20],n,m; using namespace std; void dfs(int x){ if (x>m) { d[y]=++tot; g[y][tot]=sum; ff[y][tot]=ans; return; } if (map[y][x]==1){ sum+=(1<<(x-1)); ans++; dfs(x+2); sum-=(1<<(x-1)); ans--; } dfs(x+1); } int main(){ scanf("%d%d",&n,&m); rep(i,1,n) rep(j,1,m) scanf("%d",&map[i][j]); rep(i,1,n){ tot=ans=sum=0; y=i; dfs(1); } ans=0; if (n==1) printf("%d\n",d[1]); else { rep(i,1,d[1]) f[1][i]=1; rep(i,2,n) rep(j,1,d[i]) rep(k,1,d[i-1]){ if ((g[i][j]&g[i-1][k])==0) f[i][j]=(f[i][j]+f[i-1][k])%mm; } rep(i,1,d[n]) ans=(ans+f[n][i])%mm; printf("%d",ans); } return 0; }
1231: [Usaco2008 Nov]mixup2 混乱的奶牛
http://www.lydsy.com/JudgeOnline/problem.php?id=1231
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define rep(i,l,r) for (int i=l;i<=r;i++) #define ll long long #define maxn 70000 ll f[maxn][20],mm,ans; int n,m,a[20]; int main(){ scanf("%d%d",&n,&m); rep(i,1,n) scanf("%d",&a[i]); rep(i,0,n-1) f[1<<i][i+1]=1; mm=(1<<n)-1; rep(i,0,mm) rep(j,1,n) if (1<<(j-1)&i) rep(k,1,n) if ((1<<(k-1)|i)!=i&&abs(a[k]-a[j])>m) f[i|1<<(k-1)][k]+=f[i][j]; rep(i,1,n) ans+=f[mm][i]; printf("%lld\n",ans); return 0; }
2073: [POI2004]PRZ
http://www.lydsy.com/JudgeOnline/problem.php?id=2073
主要就是枚举子集转移的时候比较神奇。其实也没什么。。。
(这个比较神奇是这样的,每次&(j-1)可以发现每次去掉了原二进制数中最后一个1,也就是砍掉了这个人。这样的好处在于保证正确性然后。。因为答案是多个集合的答案加起来,如果我从头枚举转移的子集,那时间复杂度是不允许的,但是用上面那种方式转移的话,转移的时间复杂度就只有n*2^n
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define rep(i,l,r) for (int i=l;i<=r;i++) #define ll long long #define maxn 70000 using namespace std; int mm,n,m,a[20],b[20],t[20],w[maxn],tim[maxn],f[maxn]; int main(){ scanf("%d%d",&m,&n); rep(i,1,n) scanf("%d%d",&a[i],&b[i]),t[i]=1<<(i-1); mm=(1<<n)-1; rep(i,0,mm) rep(j,1,n) if (t[j]&i) tim[i]=max(tim[i],a[j]),w[i]+=b[j]; memset(f,127/3,sizeof(f)); f[0]=0; rep(i,1,mm) for(int j=i;j;j=i&(j-1)) if (w[j]<=m) f[i]=min(f[i],tim[j]+f[i^j]); printf("%d\n",f[mm]); return 0; }