Loading

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点上,所以显然的:

\[f(i,S) = \sum_{\exists x\in [1,cntlow]\ \ S^{'}=S|2^x}f(i-1,S^{'}) \]

这里的 \(cntlow\) 指的是low点的数量,需要注意的是,在实现是一定要判断集合\(S\)的第 \(x\) 位是否是空的,不然有可能或运算是无意义的

2:

数字\(i\)出现在非low点上,那么我们理应从 \(f(i-1,S)\) 转移过来,有多少个这样的上一个状态呢,显然就是所有合法的非low点,但是如果每次都去暴力枚举合法点,时间复杂度还是会超一部分,所以我们试着去预处理在每一个状态集合\(S\)下,有多少合法的非low点,这里有个小技巧,由于我们并不知道前 \(i-1\) 个数中选了哪些数选了哪些low点,所以我们可以将集合\(S\)中包含的点一并加到预处理变量里,然后最后你发现 \(|S|\) 被抵消了,所以就是我们想要的答案了,所以形式化的:

\[f(i,S)=f(i-1,S)\cdot (cnt-i+1) \]

\(cnt\)就是合法点的数量。

所以答案就是\(f(n\cdot m,2^{cntlow}-1)\)

但是这样是会错的,而且你会发现答案偏大,思考为什么,我们再回看题面,你发现题目中要求只有标有字符\('X'\)的点才可以变成low点,但是你发现有这样的情况:在我们更新的过程中,会产生本身不应该作为low点但是满足low点特征的非low点,这其实是不符合题意的,换句话说,我们注意到我们的状态\(f\)的最终目的实际上应该是至少满足题目所给low点的方案数。这可太熟悉了,一个非常显然的容斥原理。

那么定义:\(g(S,k)\)表示钦定一定包含集合\(S\)中的low点,并且还有\(k\)个多余的蓄水池的所有方案数,那这个方案数其实也好求,只要把上面的状态更新到图上,并且带入上述dp过程,重复利用dp函数即可。

形式化的:

\[ans = \sum_{k=0}^{T-|S|}(-1)^k\cdot g(S,k) \]

其中\(T\)表示最多可以放多少个low点。

发现有大佬写了正确性证明,所以我也借鉴借鉴,以后方便考场做定海神针。

我们考虑一个多产生了\(m\)个low点的请况,记多余的low点集合为\(S_m\),那么最终在容斥多项式的第\(k\)项中\((0\leq k\leq m)\)若多出来的\(k\)个数均为集合\(S_m\)中的元素,则一定作为一个贡献出现一次,所以第\(k\)项的贡献即为\((-1)^k\cdot \dbinom{m}{k}\),所以总贡献即为:

\[\sum_{k=0}^{m}(-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;	
} 
posted @ 2025-03-05 08:25  AxB_Thomas  阅读(16)  评论(0)    收藏  举报
Title