状态压缩刷题
所谓状态压缩,大多数就是用二进制01形式将状态表示出来,运用位运算完成状态的查看和转移;基本上数据范围是n<=15;
P4906 小奔关闹钟
这是很裸的状态压缩。我们要关闭所有的开关,但是开关是相连的;
有一个很好地条件是,开关最多能波及到两层;一个开关的变化,直接关联的会变化,间接变化的也会变,但是间接变化的不会再传递下去;
所以我们将每个开关关掉后的状态记录下来;
设二进制1是关闭状态,当状态变为0时更新答案即可;
要排除自己关自己的情况;
二进制枚举关哪一个就行了;
一个开关不需要关两遍及以上,相当于没干什么;
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1e6+10; int res[maxn]; int a[30][30]; int n; int ans=2147483647; void dfs(int x,int now,int cnt) { if(x==n+1) { if(now==0) { ans=min(ans,cnt); } return ; } dfs(x+1,now,cnt); now^=res[x]; dfs(x+1,now,cnt+1); now^=res[x]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int m; scanf("%d",&m); for(int j=1;j<=m;j++) { int x; scanf("%d",&x); a[i][x]=1; } } for(int i=1;i<=n;i++) { res[i]^=(1<<i); for(int j=1;j<=n;j++) { if(a[i][j]&&i!=j) { res[i]^=(1<<j); for(int k=1;k<=n;k++) { if(a[j][k]&&j!=k) { res[i]^=(1<<k); } } } } } //int sum=0; int sum=(1<<(n+1))-2; /*for(int i=1;i<=n;i++) { sum+=1<<i; }*/ //printf("%d\n",sum); dfs(1,sum,0); if(ans==2147483647) printf("Change an alarm clock,please!"); else printf("%d",ans); return 0; }
P2622 关灯问题II
这道题的状态比上面的题多一点点,但是基本类比;不同的是我们这次用DP转移。
设f[i]为当前为i状态时,最少关灯次数;
二进制数1表示灯开着,f[0]为答案;
每次判断每个按键能控制的灯,按完后的状态为now,从按之前的转移过来;
(1<<(k-1)&now)表示当前第k个灯还亮着;
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=2e5+10; int n,m; int a[120][120]; int f[maxn]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { scanf("%d",&a[i][j]); } } memset(f,0x7f,sizeof(f)); f[(1<<n)-1]=0; for(int i=(1<<n)-1;i>=0;i--) { for(int j=1;j<=m;j++) { int now=i; for(int k=1;k<=n;k++) { if(a[j][k]==0) continue; if(a[j][k]==1&&((1<<(k-1)&now))) now^=(1<<(k-1)); else if(a[j][k]==-1&&!((1<<(k-1))&now)) now^=1<<(k-1); } f[now]=min(f[now],f[i]+1); } } if(f[0]==2139062143) printf("-1"); else printf("%d",f[0]); return 0; }
P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows
我们需要的是方案数,DP跑不了;
怎么设计状态?
设f[i][j]为第i头牛再队尾时,情况为j时的合法方案数;
二进制1表示牛在队列里;
我们枚举哪头牛再队尾的情况;
再枚举哪头牛再他的前面,状态直接搬运过来;
初始化 队列中只有一头牛的方案数为1;
枚举时判断混乱条件;
最后的答案是加和;
#include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; const int maxn=1e5+10; typedef long long ll; ll f[20][maxn]; int n,x; int a[20]; ll ans; int main() { scanf("%d%d",&n,&x); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) { f[i][1<<(i-1)]=1; } for(int i=1;i<1<<n;i++) { for(int j=1;j<=n;j++) { if(f[j][i]) continue; if(!(i&(1<<(j-1)))) continue; int now=i^(1<<(j-1)); for(int k=1;k<=n;k++) { if(j==k) continue; if(!(now&(1<<(k-1)))||abs(a[j]-a[k])<=x) continue; f[j][i]+=f[k][now]; } } } for(int i=1;i<=n;i++) { ans+=f[i][(1<<n)-1]; } printf("%lld",ans); return 0; }
P2473 [SCOI2008]奖励关
这个题是状态压缩+期望;
将每个物品的前提条件状态压缩存起来,当前状态能吃的时候比较一下即可;
设f[i][j]表示在第1轮到第i−1轮内宝物是否取过的状态为j,第i轮到第K轮的最大期望得分。
但是我们需要逆推,因为正着枚举有些状态是并没有被算出来的;
如果当前状态能选当前物品
f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<(k-1))]+(dd)p[k]);
下一轮是要选k的,才能加上p[k];
每次都要/n,因为选k的概率是1/n;
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef double dd; dd f[110][1<<15]; int k,n; int p[20]; int res[20]; int main() { scanf("%d%d",&k,&n); for(int i=1;i<=n;i++) { scanf("%d",&p[i]); int x; while(scanf("%d",&x)&&x) { res[i]|=1<<(x-1); } } for(int i=k;i>=1;i--) { for(int j=0;j<(1<<n);j++) { for(int k=1;k<=n;k++) { if((res[k]&j)==res[k]) { f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<(k-1))]+(dd)p[k]); } else f[i][j]+=f[i+1][j]; } f[i][j]/=(dd)n; } } printf("%.6lf",f[1][0]); return 0; }