BZOJ 2669 CQOI2012 局部极小值 状压dp+容斥原理
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2669
题意概述:实际上原题意很简洁了我就不写了吧。。。。
二话不说先观察一下性质,首先棋盘很窄,可以乱搞的样子,然后注意到如果一个点是局部极小值那么周围3*3矩阵内不能有另一个局部最小值。于是画个图发现题目的数据范围最多有8个局部最小值。性质大概就是这些了。
暴力实际上是搜索,本质是多阶段决策问题。由于棋盘很小,容易让人联想到搞个插头dp之类东西来弄一下,依次填每个格子来作为一个决策阶段。然后就发现。。。这个东西太复杂了。。。等你想出来不知道什么时候去了(而且很有可能想不出来)。。。于是需要换个思路,把决策阶段改一下,每一行作为阶段的话更加复杂。。。弃疗。那么决策阶段很有可能就不是按照格子的顺序来的了。注意到局部最小值一定是周围的格子里面最小的那个,并且最多只有8个局部最小值,那么尝试从小到大填充每一个数字作为阶段,把局部最小值是否填充的状态压进去。
于是令f(s,i)表示用1~i的数字填充棋盘,集合s中的局部最小值已经被填充的方案数。
分析这种决策下的性质,发现一个局部最小值一定是以其为中心3*3内最先填的那个,也就是说如果一个局部最小值没有填充,那么周围3*3的点都不能填充。排除所有不可以填充的点剩下的就是可以填充的点,令cnt[s]表示局部极小值填充状态为s时的棋盘上最多有几个数字。
得到f(s,i)=f(s,i-1)*C(cnt[s]-i+1,1)+sum{ f(s-{j},i-1) | j是s的子集 },时间复杂度O(N*M*X*2^X),X表示棋盘上有多少个局部极小值。
但是可以注意到在状态设计的时候可以保证题目给出的X都成为局部最小值,但是可能让不是局部最小值的位置变成局部最小值。
于是这题最神的地方来了:容斥!由于棋盘很小,所以说打个回溯跑一下局部极小值的分布位置发现最多也就16000多的方案。我们用回溯跑出每一种题目要求位置为局部极小值的棋盘状态,对于每个状态来一次dp,然后根据有多少个额外的点是局部极小值进行奇偶容斥就得出答案。
时间复杂度O(O(容斥)*N*M*X*2^X)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 #include<queue> 8 #include<set> 9 #include<map> 10 #include<vector> 11 #include<cctype> 12 using namespace std; 13 const int maxn=6; 14 const int maxm=9; 15 const int maxs=(1<<8)+2; 16 const int mo=12345678; 17 18 int N,M,X,ans; 19 char mp[maxn][maxm]; 20 struct XY{ int x,y; }p[10]; 21 int bin[10],f[maxs][4*7+5],cnt[maxs],vis[maxn][maxm],sz[maxs]; 22 23 void data_in() 24 { 25 scanf("%d%d",&N,&M); 26 for(int i=1;i<=N;i++){ 27 scanf("%s",mp[i]+1); 28 for(int j=1;j<=M;j++) 29 if(mp[i][j]=='X') p[++X]=(XY){i,j}; 30 } 31 for(int i=1;i<=9;i++) bin[i]=1<<i-1; 32 for(int s=1;s<bin[9];s++) sz[s]=sz[s>>1]+(s&1); 33 } 34 void dp(int tot) 35 { 36 for(int s=0;s<bin[tot+1];s++){ 37 cnt[s]=0; 38 memset(vis,0,sizeof(vis)); 39 for(int j=1;j<=tot;j++) if(!(bin[j]&s)){ 40 int x=p[j].x,y=p[j].y; 41 vis[x-1][y-1]=vis[x-1][y]=vis[x-1][y+1]= 42 vis[x][y-1]=vis[x][y]=vis[x][y+1]= 43 vis[x+1][y-1]=vis[x+1][y]=vis[x+1][y+1]=1; 44 } 45 for(int i=1;i<=N;i++) 46 for(int j=1;j<=M;j++) cnt[s]+=1-vis[i][j]; 47 } 48 memset(f,0,sizeof(f)); 49 f[0][0]=1; 50 for(int i=1;i<=cnt[0];i++) 51 f[0][i]=f[0][i-1]*(cnt[0]-i+1)%mo; 52 for(int s=1;s<bin[tot+1];s++) 53 for(int i=sz[s];i<=cnt[s];i++){ 54 f[s][i]=f[s][i-1]*(cnt[s]-i+1)%mo; 55 for(int j=1;j<=tot;j++) if(bin[j]&s) 56 f[s][i]=(f[s][i]+f[s^bin[j]][i-1])%mo; 57 } 58 } 59 bool check(int x,int y) { return mp[x-1][y-1]!='X'&&mp[x-1][y]!='X'&&mp[x-1][y+1]!='X'&&mp[x][y-1]!='X'; } 60 void dfs(int x,int y,int n) 61 { 62 if(x>N){ 63 dp(X+n); 64 if(n%2==0) ans=(ans+f[bin[X+n+1]-1][N*M])%mo; 65 else ans=(ans-f[bin[X+n+1]-1][N*M]+mo)%mo; 66 return; 67 } 68 int xx=x,yy=y+1; 69 if(yy>M) xx++,yy=1; 70 if(mp[x][y]=='X'){ 71 if(check(x,y)) dfs(xx,yy,n); 72 } 73 else{ 74 if(check(x,y)){ 75 mp[x][y]='X',p[X+n+1]=(XY){x,y}; 76 dfs(xx,yy,n+1); 77 mp[x][y]='.'; 78 } 79 dfs(xx,yy,n); 80 } 81 } 82 void work() 83 { 84 dfs(1,1,0); 85 printf("%d\n",ans); 86 } 87 int main() 88 { 89 data_in(); 90 work(); 91 return 0; 92 }