状态压缩
为什么这一篇访问量这么高啊QAQ
还是不懂鸭,明明没啥好题,写的也一般...不如顺便引个流,要不要康康蛤蛤日报?
状态压缩是个很神奇的东西,可以把复杂的状态压缩到一个简单的数来保存。类似于哈希?从某个课件上看到了这种总结感觉很不错:当状态维数很多,但总量很少时,可以将状态压缩为一个数来记录。以前以为状态压缩只能是dp,事实上配合别的算法也不错。
状压搜索:
关灯问题II https://www.luogu.org/problemnew/show/P2622
题意概述:n盏灯,m种操作,可以使每个灯的状态出现改变,问最少的操作数。
N<=10,所以可以用二进制保存目前有哪些灯是开的,然后就可以广搜啦。
# include <cstdio> # include <iostream> using namespace std; int n,m,ans=0,l[101][11]; int no,ne,q[2000],h=1,t=2; int vis[2000],f; void bfs() { while(h<t) { no=q[h++]; for (int i=1;i<=m;i++) { ne=no; for (int j=0;j<n;j++) { if(l[i][j]==1) if(ne&(1<<j)) ne-=(1<<j); if(l[i][j]==-1) ne=ne|(1<<j); } if(vis[ne]==-1) { q[t++]=ne; vis[ne]=vis[no]+1; } if(ne==0) { f=true; ans=vis[ne]; return ; } } } } int main() { scanf("%d%d",&n,&m); for (int i=0;i<=1999;i++) vis[i]=-1; no=(1<<n)-1; for (int i=1;i<=m;i++) for (int j=0;j<n;j++) scanf("%d",&l[i][j]); q[h]=no; vis[no]=0; bfs(); if(f) printf("%d",ans); else printf("-1"); return 0; }
八数码难题:https://www.luogu.org/problemnew/show/P1379
题意概述:给定放好了八个棋子一个空格的棋盘,每次可以移动空格,求移动到目标棋盘的最少步数。
这真是个好题,在搜索-1,搜索-2中还会有进一步实现。
BFS!怎么保存棋盘,康拓展开。虽然不是一般的01状压,但是却符合维数很多总量较少的定义。不过对于这道题不用康拓展开,因为只有九个数字,所以暴力拽成一条链也是可以的。(也可以用set)。
# include <iostream> # include <set> # include <queue> # include <cstdio> using namespace std; const int dx[]={-1,0,0,1}; const int dy[]={0,1,-1,0}; int x,dream=123804765; int ans=-1; int q[10000000]={0}; int ql[10000000]={0}; set<int> s; int A() { int head=0,tail=1; while (1) { int il=0,jl=0,n[3][3],now; now=q[++head]; if(now==dream) return ql[head]; for (register int i=2;i>=0;i--) for (register int j=2;j>=0;j--) { if(now%10==0) il=i,jl=j; n[i][j]=now%10; now=now/10; } for (register int i=0;i<4;i++) { int xx=il+dx[i]; int yy=jl+dy[i]; if(xx>=0&&xx<=2&&yy>=0&&yy<=2) { int nn[3][3]; for (register int ii=0;ii<3;ii++) for (register int jj=0;jj<3;jj++) nn[ii][jj]=n[ii][jj]; swap(nn[il][jl],nn[xx][yy]); now=nn[0][0]*100000000+nn[0][1]*10000000+nn[0][2]*1000000+nn[1][0]*100000+nn[1][1]*10000+nn[1][2]*1000+nn[2][0]*100+nn[2][1]*10+nn[2][2]; if(s.find(now)!=s.end()) { continue; } else { s.insert(now); q[++tail]=now; ql[tail]=ql[head]+1; } } } } } int main() { scanf("%d",&x); q[1]=x; ql[1]=0; s.insert(x); ans=A(); printf("%d",ans); return 0; }
宝藏:https://www.luogu.org/problemnew/show/P3959
题意概述:NOIP 2017 D2T2。
看到$n<=12$感觉还是比较好的,应该就是搜索或者状压了,思索一下觉得状压搜索也是很可行的,而且非常可做,于是飞快的写了一个交上去:70;其实我觉得70很不错啦,下面是代码:
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # define R register int 5 6 struct edge 7 { 8 int too,nex,co; 9 }g[1000]; 10 int h,n,m,x,y,v,firs[15],ans=0,k[15]; 11 int G[15][15]; 12 13 int min(int a,int b) 14 { 15 if(a<b) return a; 16 return b; 17 } 18 19 void add (int x,int y,int v) 20 { 21 g[++h].too=y; 22 g[h].co=v; 23 g[h].nex=firs[x]; 24 firs[x]=h; 25 } 26 27 void dfs(int co,int q) 28 { 29 if(co>=ans) return ; 30 if(q==(1<<n)-1) 31 { 32 ans=min(ans,co); 33 return ; 34 } 35 int a; 36 for (R i=1;i<=n;++i) 37 { 38 if(!(q&(1<<(i-1)))) continue; 39 for (R j=firs[i];j;j=g[j].nex) 40 { 41 a=g[j].too; 42 if(q&(1<<(a-1))) continue; 43 k[a]=k[i]+1; 44 dfs(co+k[i]*g[j].co,q|(1<<(a-1))); 45 } 46 } 47 } 48 49 int main() 50 { 51 memset(G,127,sizeof(G)); 52 ans=G[1][1]; 53 scanf("%d%d",&n,&m); 54 for (R i=1;i<=m;++i) 55 { 56 scanf("%d%d%d",&x,&y,&v); 57 G[x][y]=G[y][x]=min(G[x][y],v); 58 } 59 for (R i=1;i<=n;++i) 60 for (R j=1;j<=n;++j) 61 if(G[i][j]<=500000) add(i,j,G[i][j]); 62 for (R i=1;i<=n;++i) 63 { 64 memset(k,0,sizeof(k)); 65 k[i]=1; 66 dfs(0,1<<(i-1)); 67 } 68 printf("%d",ans); 69 return 0; 70 }
进行一番神奇的记忆化之后A掉了:dp[i]表示生成树中包含i这个集合的最小花费。但是这个不大对啊qwq 虽然并不能举出反例来,但是感性理解觉得不大对。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # define R register int 6 7 struct edge 8 { 9 int too,nex,co; 10 }g[1000]; 11 int h,n,m,x,y,v,firs[15],ans=0,k[15]; 12 int G[15][15]; 13 int vis[100000]; 14 long long timee=0; 15 16 int min(int a,int b) 17 { 18 if(a<b) return a; 19 return b; 20 } 21 22 void add (int x,int y,int v) 23 { 24 g[++h].too=y; 25 g[h].co=v; 26 g[h].nex=firs[x]; 27 firs[x]=h; 28 } 29 30 void dfs(int co,int q) 31 { 32 if(co>=ans) return ; 33 if(q==(1<<n)-1) 34 { 35 ans=min(ans,co); 36 return ; 37 } 38 if(timee>=10000000) return ; 39 int a; 40 if(n>8) 41 { 42 if(vis[q]<=co) return ; 43 vis[q]=co; 44 } 45 for (R i=1;i<=n;++i) 46 { 47 if(!(q&(1<<(i-1)))) continue; 48 for (R j=firs[i];j;j=g[j].nex) 49 { 50 a=g[j].too; 51 52 timee++; 53 if(timee>=10000000) return ; 54 55 if(q&(1<<(a-1))) continue; 56 k[a]=k[i]+1; 57 dfs(co+k[i]*g[j].co,q|(1<<(a-1))); 58 } 59 } 60 } 61 62 int main() 63 { 64 memset(G,127,sizeof(G)); 65 ans=G[1][1]; 66 scanf("%d%d",&n,&m); 67 for (R i=1;i<=m;++i) 68 { 69 scanf("%d%d%d",&x,&y,&v); 70 G[x][y]=G[y][x]=min(G[x][y],v); 71 } 72 for (R i=1;i<=n;++i) 73 for (R j=1;j<=n;++j) 74 if(G[i][j]<=500000) add(i,j,G[i][j]); 75 memset(vis,127,sizeof(vis)); 76 for (R i=1;i<=n;++i) 77 { 78 memset(k,0,sizeof(k)); 79 k[i]=1; 80 dfs(0,1<<(i-1)); 81 } 82 printf("%d",ans); 83 return 0; 84 }
状压线段树:
色板问题 https://www.luogu.org/problemnew/show/P1558
题意概述:在序列上进行区间覆盖和查询,查询这段区间的元素种类。
看起来很像SDOI某一年的HH的项链,但是这道题支持修改就很不一样了。注意到元素种类非常少,最多才30种,所以可以进行状态压缩,update的时候可以或一下,pushdown的时候直接覆盖。
# include <cstdio> # include <iostream> # define R register int # define nl n<<1 # define nr n<<1|1 using namespace std; const int maxn=100009; int xx,cc,n,t,o,l,r,co,s; long long c[maxn<<2],delta[maxn<<2],ans; char ch; void build(int n,int l,int r) { delta[n]=-1; if(l==r) { c[n]=1; return ; } int mid=(l+r)>>1; build(nl,l,mid); build(nr,mid+1,r); c[n]=c[nl]|c[nr]; } void pushdown(int n,int l,int r) { int x=delta[n]; delta[n]=-1; delta[nl]=x; delta[nr]=x; c[nl]=(1<<(x-1)); c[nr]=(1<<(x-1)); c[n]=c[nl]|c[nr]; } void chan (int n,int l,int r,int ll,int rr,int co) { if(ll<=l&&r<=rr) { delta[n]=co; c[n]=(1<<(co-1)); return ; } if(delta[n]!=-1) pushdown(n,l,r); int mid=(l+r)>>1; if(ll<=mid)chan(nl,l,mid,ll,rr,co); if(rr>mid)chan(nr,mid+1,r,ll,rr,co); c[n]=c[nl]|c[nr]; } long long ask (int n,int l,int r,int ll,int rr) { if(ll<=l&&r<=rr) return c[n]; if(delta[n]!=-1) pushdown(n,l,r); int mid=(l+r)>>1; long long ans=0; if(ll<=mid) ans=ans|ask(nl,l,mid,ll,rr); if(rr>mid) ans=ans|ask(nr,mid+1,r,ll,rr); return ans; } int read() { xx=0; cc=getchar(); while (!isdigit(cc)) cc=getchar(); while (isdigit(cc)) xx=(xx<<3)+(xx<<1)+(cc^48),cc=getchar(); return xx; } int main() { n=read(),t=read(),o=read(); build(1,1,n); for (R i=1;i<=o;i++) { ch=getchar(); while (ch!='P'&&ch!='C') ch=getchar(); if(ch=='C') { l=read(),r=read(),co=read(); if(l>r) swap(l,r); chan(1,1,n,l,r,co); } else { l=read(),r=read(); if(l>r) swap(l,r); ans=ask(1,1,n,l,r); s=0; for (int i=0;i<=t;i++) if (ans&(1<<i)) s++; printf("%d\n",s); } } return 0; }
状压DP:
玉米田:https://www.luogu.org/problemnew/show/P1879
题意概述:从给定的01网格中选出一些1点,且四不连通,求方案数。
突破点依旧在mn的范围很小,可以考虑按行转移(当然也可以按列),每一行的总状态数应从0循环到(1<<n)-1,按行转移时注意判断一下此状态是否合法,且与上一行的状态是否冲突。可以预处理每一行的所有合法状态,循环时外层循环本行状态,一旦不合法就剪掉,对于这道题可以优化到0ms。 O(2^(2*n)*n)去R2逛了一圈后深刻的认识到数据范围加0的恐怖,于是手动给这道题加0。然而加1个零我就不会做了,毕竟复杂度上界会爆炸。然而这道题的复杂度必然是非常不准的,因为每一层的状态不可能有2^n种那么多。
# include <cstdio> # include <iostream> # define R register int using namespace std; const int Mod=100000000; int m,n; int g[13][13]; long long dp[13][5000]; bool ca[13][5000]; inline bool check(int h,int x) { for (R i=1;i<=n;i++) if((x&(1<<(i-1)))&&g[h][i]==0) return false; for (R i=1;i<n;i++) if((x&(1<<(i-1)))&&(x&(1<<i))) return false; return true; } int main() { scanf("%d%d",&m,&n); for (R i=1;i<=m;i++) for (R j=n;j>=1;j--) scanf("%d",&g[i][j]); for (R i=1;i<=m;i++) for (R j=0;j<(1<<n);j++) { ca[i][j]=check(i,j); if(ca[i][j]&&i==1) dp[i][j]=1; } for (R i=2;i<=m;i++) for (R no=0;no<(1<<n);no++) { if(!ca[i][no]) continue; for (R j=0;j<(1<<n);j++) { if(no&j) continue; dp[i][no]=(dp[i][no]+dp[i-1][j])%Mod; } } long long ans=0; for (R i=0;i<(1<<n);i++) ans=(ans+dp[m][i])%Mod; printf("%lld",ans%Mod); return 0; }
互不侵犯:https://www.luogu.org/problemnew/show/P1896
题意概述:在n*n的网格中选出k个点,使得任意两点间八不连通。
感觉和上一题很像,再加一维保存下目前有几个国王,判断时左移或一次,右移或一次,不动再或一次就可以了。
又到了愉快的加0时间,对于状压dp我已经放弃加0了,如果没有新的思路其实很难再优化,现在觉得能多+1几次已经很是进步了,而且题解也没有特别的思路,优化一下常数的话或许能快一点。其实这道题也可以跑很大的数据,因为它的输入量非常小,近乎于输入n输出答案的题目,于是自然可以想到。。。打表!慢慢跑出答案存个表的话差不多可以加2个零呢!
# include <cstdio> # include <iostream> # define R register int using namespace std; int s=0,n,k,mn,aa; long long S=0; long long dp[10][1000][100]; bool ca[1000]; inline int Hoone(int x) { s=0; for (R i=1;i<=n;i++) if(x&(1<<(i-1))) s++; return s; } inline bool check(int x) { for (R i=1;i<n;i++) if((x&(1<<(i-1)))&&(x&(1<<i))) return false; return true; } int main() { scanf("%d%d",&n,&k); mn=(1<<n)-1; for (R j=0;j<=mn;j++) { if(check(j)) ca[j]=true; if(ca[j]) dp[1][j][Hoone(j)]=1; } for (R i=2;i<=n;i++) for (R no=0;no<=mn;no++) { if(ca[no]==false) continue; for (R j=0;j<=mn;j++) for (R h=0;h<=k;h++) { if(dp[i-1][j][h]==0) continue; if((no<<1)&j) continue; if(no&j) continue; if(no&(j<<1)) continue; aa=h+Hoone(no); if(aa>k) continue; dp[i][no][aa]+=dp[i-1][j][h]; } } for (R no=0;no<=mn;no++) S+=dp[n][no][k]; printf("%lld",S); return 0; }
选数游戏:https://www.luogu.org/problemnew/show/P1123
题意概述:在网格中选出一些数,使其八不连通且和最大。
这道题的数据范围很小,正解是搜索,写状压有炫技的嫌疑。。。主要还是想练一下状压。首先预处理出每一行的合法情况,再处理一下第一行的答案,按行转移即可。然而只有一行可情况不要忘了,ans要从预处理第一行时就开始统计答案。。。
# include <cstdio> # include <iostream> # include <cstring> using namespace std; int T; int n,m,ans=0; int a[7][7]; int vis[7][70]; int dp[7][70]; void check(int i,int j) { if((j<<1)&j) { vis[i][j]=-1; return ; } if((j>>1)&j) { vis[i][j]=-1; return ; } for (int x=1;x<=m;x++) if(j&(1<<(x-1))) vis[i][j]+=a[i][x]; } int main() { scanf("%d",&T); while (T) { ans=0; memset(a,0,sizeof(a)); memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) scanf("%d",&a[i][j]); for (int i=1;i<=n;i++) for (int j=0;j<(1<<m);j++) check(i,j); m=(1<<m)-1; for (int i=0;i<=m;i++) { dp[1][i]=vis[1][i]; ans=max(ans,dp[1][i]); } for (int i=2;i<=n;i++) for (int j=0;j<=m;j++) for (int k=0;k<=m;k++) { if(j&k) continue; if((j<<1)&k) continue; if((j>>1)&k) continue; if(vis[i][j]==-1) continue; if(vis[i-1][k]==-1) continue; dp[i][j]=max(dp[i][j],dp[i-1][k]+vis[i][j]); ans=max(ans,dp[i][j]); } printf("%d\n",ans); T--; } return 0; }
2018.8.8:今天老师让提交一下近期做的dp题,结果发现最近做的全是dp题,咕咕咕。
愤怒的小鸟:https://www.luogu.org/problemnew/show/P2831
题意概述:从原点发射小鸟打n只猪,求最小的小鸟数量。(n<=18)
其实这题还是比较好想的,n<=18,几乎可以肯定是状压或者搜索,$18!$看起来有点大,但是$2^{18}$好像海星,所以就状压。
设$dp[x]$表示打掉x这个集合所需的最小小鸟数量,考虑转移。抛物线的数量是无限的,但是有的抛物线显然非常不优,只打一只猪一定可以用一只小鸟,而且两点无法确定一条抛物线,所以干脆预处理出来:$dp[1<<(i-1]=1$,剩下的抛物线就至少可以打两只猪了,枚举这两只猪求出抛物线解析式,再看看其他那些猪可以被这一条抛物线一起打到,就找出了所有有用的抛物线。感觉dp并不是非常好写,所以就写了一个记忆化搜索,顺便学了一个$n^3$枚举子集的方法,愉快的交上去,结果又WA又T,这真是非常伤心。后来发现是卡精度了,就改了一下。改到75之后发现不再是精度问题了。冷静分析,发现是预处理的问题。比如说有一条抛物线是111101,但是这次的转移只需要100101,这个状态反而被算成了2条抛物线,其实是不对的,于是又对每条抛物线进行了枚举子集,果然不再错了,(但是T了)。这时候我不得不承认这个做法虽然看起来精妙,时间复杂度却不对,使用递推可能还好一点。至此,$O(2^n*n^3)$的做法正式宣布破产。
看到讨论区有人说爆搜都比这个快,放弃梦想写了一个大爆搜,然而真的过了,而且非常非常快,原来要跑4.5s的数据现在...只要4ms?搜索的复杂度果然玄学。后来仔细想一想发现并不玄学,在最坏的情况下一共要搜索$2^n$种状态,而对于每种状态可以确定一个抛物线上的点,再枚举一个,所以总复杂度的上界就是$O(2^n*n)$,然后也再加上一点点玄学,自然就非常快了。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # include <cmath> 6 # define R register int 7 8 using namespace std; 9 10 double eps=0.000001; 11 int T,n,m,s; 12 int dp[302144]; 13 bool vis[302144]; 14 double x[20],a,b; 15 double y[20]; 16 17 void solve (int i,int j) 18 { 19 a=(y[i]*x[j]-y[j]*x[i])/(x[i]*x[i]*x[j]-x[j]*x[j]*x[i]); 20 b=(y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[j]*x[i]*x[i]);} 21 22 bool xd (double a,double b) 23 { 24 double c=a-b; 25 if(c<0) c=-c; 26 return c<(1e-6); 27 } 28 29 int dfs (int x) 30 { 31 if(vis[x]) return dp[x]; 32 int ans=dp[x]; 33 vis[x]=true; 34 for(R t=x;t;t=(t-1)&x) 35 { 36 if(ans==1) break; 37 ans=min(ans,dfs(t)+dfs(x-t)); 38 } 39 dp[x]=ans; 40 return ans; 41 } 42 43 int main() 44 { 45 scanf("%d",&T); 46 while (T--) 47 { 48 scanf("%d%d",&n,&m); 49 memset(dp,127,sizeof(dp)); 50 memset(vis,0,sizeof(vis)); 51 dp[0]=0; 52 for (R i=1;i<=n;++i) 53 scanf("%lf%lf",&x[i],&y[i]); 54 for (R i=1;i<=n;++i) 55 dp[1<<(i-1)]=1,vis[1<<(i-1)]=1; 56 for (R i=1;i<=n;++i) 57 for (R j=1;j<=n;++j) 58 { 59 if(i==j) continue; 60 s=0; 61 if(xd(x[i],x[j])) continue; 62 solve(i,j); 63 if(a>(-1e-6)) 64 continue; 65 for (R k=1;k<=n;++k) 66 if(xd(x[k]*x[k]*a+x[k]*b,y[k])) 67 s|=(1<<(k-1)); 68 for (int t=s;t;t=(t-1)&s) 69 vis[t]=true,dp[t]=1; 70 } 71 printf("%d\n",dfs((1<<n)-1)); 72 } 73 return 0; 74 }
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # include <cmath> 6 # define R register int 7 # define eps 0.000001 8 9 int T,n,m,s,fans; 10 int g[20][20]; 11 int dp[262244]; 12 double x[20],a,b; 13 double y[20]; 14 15 using namespace std; 16 17 inline void solve (int i,int j) 18 { 19 a=(y[i]*x[j]-y[j]*x[i])/(x[i]*x[i]*x[j]-x[j]*x[j]*x[i]); 20 b=(y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[j]*x[i]*x[i]); 21 } 22 23 inline bool xd (double a,double b) 24 { 25 double c=a-b; 26 if(c<0) c=-c; 27 return c<eps; 28 } 29 30 int logg (int x) 31 { 32 int a=1; 33 while (x!=(1<<(a-1))) 34 a++; 35 return a; 36 } 37 38 inline void dfs (int x,int ans) 39 { 40 if(dp[x]<=ans) return ; 41 dp[x]=ans; 42 if(ans>=fans) return; 43 int q=(1<<n)-1,b=q-x; 44 if(!b) 45 { 46 fans=min(ans,fans); 47 dp[q]=fans; 48 return ; 49 } 50 int i=b&(-b); 51 i=logg(i); 52 for (int j=1;j<=n;++j) 53 dfs(x|g[i][j],ans+1); 54 } 55 56 int main() 57 { 58 scanf("%d",&T); 59 while (T--) 60 { 61 scanf("%d%d",&n,&m); 62 memset(g,0,sizeof(g)); 63 memset(dp,127,sizeof(dp)); 64 for (R i=1;i<=n;++i) 65 scanf("%lf%lf",&x[i],&y[i]); 66 for (R i=1;i<=n;++i) 67 g[i][i]=1<<(i-1); 68 for (R i=1;i<=n;++i) 69 for (R j=i;j<=n;++j) 70 { 71 if(i==j) continue; 72 s=0; 73 if(xd(x[i],x[j])) continue; 74 solve(i,j); 75 if(a>(-eps)) 76 continue; 77 for (R k=1;k<=n;++k) 78 if(xd(x[k]*x[k]*a+x[k]*b,y[k])) 79 s|=(1<<(k-1)); 80 g[i][j]=g[j][i]=s; 81 } 82 fans=1000; 83 dfs(0,0); 84 printf("%d\n",fans); 85 } 86 87 return 0; 88 }
1 for(R t=x;t;t=(t-1)&x) (枚举子集)
NOIP2018后补充:状态压缩太可怕了,我D2死磕T2的状压做法(显然根本不对),导致全面崩盘。
有的同学说我一直不更博,其实只是更到老文章的后面了。--2019.3.29
按位或:https://www.luogu.org/problemnew/show/P3175
题意概述:刚开始你有一个数字 $0$,每一秒钟你会随机选择一个 $[0,2^n-1]$ 的数字,与你手上的数字进行或操作。选择数字i的概率是 $p_i$。保证 $0<=p_i<=1$,$\sum p_i=1$问期望多少秒后,你手上的数字变成 $2^n-1$。(其实这就是抄了题面并加上了latex.)
今天看了看luogu智推:多项式,多项式,数据机构,多项式,“按位或”;那就看看这道没见过的题目吧。
哇luogu竟然推了一道看起来很不错的状压dp给我,好良心。好的,那我们按照套路,设 $dp_i$ 表示当前状态为 $i$ 时期望还要走几步才能结束,边界情况显然,转移的话列个方程就好了(有可能转移到自己) $dp_i=\sum_{j}f(j)dp(i|j)+1$.嗯嗯,这样就做完了,看看数据范围吧...n<=20 ?可是这个做法是 $4^n$,好没有前途啊。得了30.点开题解想看看有什么神奇优化,结果:“min-max容斥+fwt”...原来luogu是觉得“fwt”和“fft”念起来很像才给我推这题的吗?
1 # include <cstdio> 2 # include <iostream> 3 # define R register int 4 5 using namespace std; 6 7 const int maxn=(1<<20)+5; 8 const double eps=1e-10; 9 int n; 10 double f[maxn],dp[maxn],vis[40]; 11 12 int main() 13 { 14 scanf("%d",&n); 15 for (R i=0;i<(1<<n);++i) 16 { 17 scanf("%lf",&f[i]); 18 for (R j=0;j<n;++j) 19 if(i&(1<<j)) vis[j]+=f[i]; 20 } 21 for (R i=0;i<n;++i) 22 if(vis[i]<eps) { puts("INF"); return 0; } 23 for (R i=(1<<n)-2;i>=0;--i) 24 { 25 double t1=0,t2=0; 26 for (R j=0;j<(1<<n);++j) 27 { 28 if((j|i)!=i) t1+=f[j]*dp[j|i]; 29 else t2+=f[j]; 30 } 31 dp[i]=(t1+1)/(1-t2); 32 } 33 printf("%.10lf",dp[0]); 34 return 0; 35 }
正解是min-max容斥,虽然我后来学会了,但是考虑到这篇文章的标题,就只粘一个代码吧。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # define R register int 5 6 using namespace std; 7 8 const int maxn=(1<<20)+5; 9 const double eps=1e-10; 10 int n,f[maxn]; //容斥系数 11 double p[maxn],vis[40],g[maxn]; 12 13 int cal (int x) 14 { 15 int ans=0; 16 for (R i=0;i<n;++i) if(x&(1<<i)) ans++; 17 return ans; 18 } 19 20 int main() 21 { 22 scanf("%d",&n); 23 for (R i=0;i<(1<<n);++i) 24 { 25 f[i]=cal(i); 26 if(f[i]%2) f[i]=1; 27 else f[i]=-1; 28 } 29 for (R i=0;i<(1<<n);++i) 30 { 31 scanf("%lf",&p[i]); 32 for (R j=0;j<n;++j) 33 if(i&(1<<j)) vis[j]+=p[i]; 34 } 35 for (R i=0;i<n;++i) 36 for (R j=0;j<(1<<n);++j) 37 if(j&(1<<i)) p[j]+=p[j^(1<<i)]; 38 for (R i=0;i<n;++i) 39 if(vis[i]<eps) { puts("INF"); return 0; } 40 for (R i=1;i<(1<<n);++i) 41 g[i]=1/(1-p[((1<<n)-1)^i]); 42 double ans=0; 43 for (R i=0;i<(1<<n);++i) 44 ans+=f[i]*g[i]; 45 printf("%.10lf",ans); 46 return 0; 47 }
---shzr