P3160 [CQOI2012] 局部极小值 题解
零.题目大意:
将\([1,n\cdot m]\)中的所有整数分别放入\(n\times m\)的格子矩阵里,其中的一些格子有特殊要求(周围的8个格子都要比它大,输入中体现为字符'X'),问方案数
一.思路:
这数据实在是太小了,而且又有填点的方案书与状态,很难不想到状压dp,所以一个朴素的想法是我们定义\(f(i,S)\)表示当前考虑到第i个格子,前面填点的状态。
但是你发现这样的复杂度是\(O(根本不是人)\),所以再在题目中注意一些东西,我们发现,数字与格子最终一定是一一映射的,并且这个题目的关键在于特殊点(下面统一叫做low点),所以尝试着定义:\(f(i,S)\)表示考虑填到第\(i\)个数字,并且所有low点的状态集合为\(S\)的方案数
考虑转移,容易发现,数字\(i\)有可能出现在low点或非low点,那么以此来转移。
转移
1:
数字\(i\)出现在low点上,所以显然的:
这里的 \(cntlow\) 指的是low点的数量,需要注意的是,在实现是一定要判断集合\(S\)的第 \(x\) 位是否是空的,不然有可能或运算是无意义的
2:
数字\(i\)出现在非low点上,那么我们理应从 \(f(i-1,S)\) 转移过来,有多少个这样的上一个状态呢,显然就是所有合法的非low点,但是如果每次都去暴力枚举合法点,时间复杂度还是会超一部分,所以我们试着去预处理在每一个状态集合\(S\)下,有多少合法的非low点,这里有个小技巧,由于我们并不知道前 \(i-1\) 个数中选了哪些数选了哪些low点,所以我们可以将集合\(S\)中包含的点一并加到预处理变量里,然后最后你发现 \(|S|\) 被抵消了,所以就是我们想要的答案了,所以形式化的:
\(cnt\)就是合法点的数量。
所以答案就是\(f(n\cdot m,2^{cntlow}-1)\)
但是这样是会错的,而且你会发现答案偏大,思考为什么,我们再回看题面,你发现题目中要求只有标有字符\('X'\)的点才可以变成low点,但是你发现有这样的情况:在我们更新的过程中,会产生本身不应该作为low点但是满足low点特征的非low点,这其实是不符合题意的,换句话说,我们注意到我们的状态\(f\)的最终目的实际上应该是至少满足题目所给low点的方案数。这可太熟悉了,一个非常显然的容斥原理。
那么定义:\(g(S,k)\)表示钦定一定包含集合\(S\)中的low点,并且还有\(k\)个多余的蓄水池的所有方案数,那这个方案数其实也好求,只要把上面的状态更新到图上,并且带入上述dp过程,重复利用dp函数即可。
形式化的:
其中\(T\)表示最多可以放多少个low点。
发现有大佬写了正确性证明,所以我也借鉴借鉴,以后方便考场做定海神针。
我们考虑一个多产生了\(m\)个low点的请况,记多余的low点集合为\(S_m\),那么最终在容斥多项式的第\(k\)项中\((0\leq k\leq m)\)若多出来的\(k\)个数均为集合\(S_m\)中的元素,则一定作为一个贡献出现一次,所以第\(k\)项的贡献即为\((-1)^k\cdot \dbinom{m}{k}\),所以总贡献即为:
这是一个非常常见的组合表达式,注意到,只有满足\([m=0]=1\)这样的情况下,才可以使得最终答案有贡献,总贡献为\(1\),而其余情况总贡献都因为这个等式的一个著名事实变成了\(0\)。
这其实也就意味着除了没有多选的方案,其余的都被消去了,而这就是我们希望的结果。
此外,数据范围只支持这个图上最多只有\(8\)个low点,所以容斥的复杂度是不高的。
其次是在更新图上状态时,我们用dfs回溯是最方便的选择,利用重新计算好的dp值,在dfs中更新即可。
二:code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define int long long
const int N = 12;
const int MOD = 12345678;
int n,m,lowx[N],lowy[N],cnt_low,f[2 * N + 10][(1 << N)],num,ans;
int dx[10] = {0,1,1,-1,-1,0,0,-1,1};
int dy[10] = {0,1,-1,1,-1,1,-1,0,0};
bool map[N][N],exsit[N][N];
inline bool check(int x,int y){
return x <= n && y <= m && x > 0 && y > 0;
}
int calc_dp()
{
memset(f,0,sizeof(f));
cnt_low = 0;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
if(map[i][j])
{
lowx[++cnt_low] = i;
lowy[cnt_low] = j;
}
f[0][0] = 1;
for(int S = 0;S < (1 << cnt_low);++S)
{
int cnt = n * m;
memset(exsit,false,sizeof(exsit));
for(int i = 1;i <= cnt_low;++i)
{
if(!(S & (1 << (i - 1))))
{
if(!exsit[lowx[i]][lowy[i]])
{
exsit[lowx[i]][lowy[i]] = true;
--cnt;
for(int j = 1;j <= 8;++j)
{
int tx = lowx[i] + dx[j];
int ty = lowy[i] + dy[j];
if(check(tx,ty))
if(!exsit[tx][ty])
{
exsit[tx][ty] = true;
--cnt;
}
}
}
}
}
for(int i = 0;i <= cnt;++i)
{
if(f[i][S])
{
f[i + 1][S] = (f[i + 1][S] + f[i][S] * std::max(cnt - i,0ll) % MOD) % MOD;
for(int j = 1;j <= cnt_low;++j)
{
if(!(S & (1 << (j - 1))))
f[i + 1][S | (1 << (j - 1))] = (f[i + 1][S | (1 << (j - 1))] + f[i][S]) % MOD;
}
}
}
}
return f[num][(1 << cnt_low) - 1];
}
void dfs(int x,int y,int op)
{
if(x > n){
ans = (ans + calc_dp() * op + MOD) % MOD;
return ;
}
if(y > m){
dfs(x + 1,1,op);
return ;
}
dfs(x,y + 1,op);
if(!map[x][y])
{
bool flag = true;
for(int i = 1;i <= 8;++i)
{
int tx = x + dx[i];
int ty = y + dy[i];
if(check(tx,ty) && map[tx][ty]) flag = false;
}
if(flag)
{
map[x][y] = true;
dfs(x,y + 1,-op);
map[x][y] = false;//记得回溯
}
}
return ;
}
signed main()
{
scanf("%d%d", &n, &m);
num = n * m;
for(int i = 1;i <= n;++i)
{
char s[11];
scanf("%s",s + 1);
for(int j = 1;j <= m;++j)
if(s[j] == 'X')
map[i][j] = true;
}
for(int i = 1;i <= n;++i)
{
for(int j = 1;j <= m;++j)
{
if(map[i][j])
for(int k = 1;k <= 8;++k)
{
if(check(i + dx[k],j + dy[k]))
if(map[i + dx[k]][j + dy[k]]) return putchar('0') && 0;
}
}
}
dfs(1,1,1);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号