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
",ans);
86 }
87 int main()
88 {
89     data_in();
90     work();
91     return 0;
92 }
原文地址:https://www.cnblogs.com/KKKorange/p/8478183.html