【LOJ#3197】【eJOI2019】T形覆盖 - (图论、简单推导)

题面

题解

(题目中说的四种摆放方式实际上是分别旋转0°,90°,180°,270°后的图形)

题目中关于摆放方式的描述听起来很臭,我们把它转换一下,每个拼版先覆盖“上下左右中”五个格子,然后再在四个相邻格子中减去一个。

那么我们先把每个拼版所在“十字”涂了,然后把有重复涂过的格子当成边,把拼版们通过这些边连起来,成为许多个连通块,不同连通块之间肯定是互不干扰的,可以独立计算贡献。

如下图(我用颜色的中和表示被涂多次,很好理解吧),A、B、C是连通块,注意,D不是连通块,这也是遍历的时候需要注意的情况。

对于每个连通块,里面都会有至少一个的被涂了大于等于两遍的格子,如果有被涂了三遍甚至四遍的格子,那么直接输出No,这比较显然,自己想想就知道了。

由于接下来每个拼版要在相邻的格子中减去一个,即让相邻的一个格子被涂次数-1,所以把每个连通块被涂多次的格子数数出来,记为 E(其实就是边数),把拼版数记为 N(其实就是点数),我们会发现以下结论:

  •  若 E > N ,由于一个点可以使 E 减 1 ,N 个就可以减 N,所以 E - N > 0 意味着什么?无论如何连通块内都会存在被重复覆盖的格子,即无解,输出No
  •  若 E == N,即 E - N == 0,刚好可以把重复格子清完,即为环或基环树,就如上图的A、B、C(A、B也是刚好相等的!也就是说“两个点可以有重边”),也就是说答案就是这个连通块覆盖的所有格子中的数的和,不多不少
  •  若 E < N,此时只可能是 E == N-1,即为一个树状图,此时不仅可以把被涂的多余全消完(此多余层可以为拼版中心格子),还可以多消一个非拼版中心的格子(包括先前被涂多次,后来被消成单层的格子),然后,任何连通块内这样的格子都可以被选择作为多消掉的一个,为什么呢?因为它是树形结构,所以相邻两块拼版最多有一个格子重复覆盖(不然超过一个就有环了嘛),把任意一个可消的格子选择后,所在的一个或两个拼版状态就确定了,就可以顺推出整棵树的每个拼版的状态,而且可以证明是一定有解的!……

……只不过你得特殊处理一下贴墙的情况,不过不影响结论。

至于怎么DFS,只用模拟走边,跑图,然后计算覆盖的格子的信息就行了。如果是 E < N ,就选一个权值最小的可选格子令ans减去它即可。注意ans是最早先不重复地把覆盖的所有格子的数加进去了的。

复杂度O(nm)

CODE

如果测大数据时运行爆了,不用怕,开无限栈应该就解决了

#include<vector> 
#include<cstdio> 
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1000005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) ((-x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
vector<int> a[MAXN];
int c[MAXN],d[MAXN];
bool f[MAXN];
bool Out(int x,int y) {return x < 1 || y < 1 || x > n || y > m;}
int cg(int x,int y) {return max(0,min((x-1)*m+y,n*m+1));}
struct it{
	int x,y;
}p[MAXN];
int md,cn,flag,mi,cnt;
void dfs(int x,int y) {
	if(!flag || Out(x,y) || f[cg(x,y)] || (!c[cg(x,y)])) return ;
	f[cg(x,y)] = 1;
	if(c[cg(x,y)] > 2) {
		flag = 0; return ;
	}
	if(c[cg(x,y)] == 2) md ++;
	if(d[cg(x,y)]) {
		cn ++;
		if(Out(x-1,y)) md ++;
		if(Out(x+1,y)) md ++;
		if(Out(x,y+1)) md ++;
		if(Out(x,y-1)) md ++;
	}
	else mi = min(mi,a[x][y]);
	if(c[cg(x,y)] == 2) {
		if(!Out(x-1,y) && d[cg(x-1,y)]) dfs(x-1,y);
		if(!Out(x+1,y) && d[cg(x+1,y)]) dfs(x+1,y);
		if(!Out(x,y-1) && d[cg(x,y-1)]) dfs(x,y-1);
		if(!Out(x,y+1) && d[cg(x,y+1)]) dfs(x,y+1);
	}
	else if(d[cg(x,y)]) {
		dfs(x-1,y);dfs(x+1,y);
		dfs(x,y-1);dfs(x,y+1);
	}
	return ;
}
int solve(int x,int y) {
	mi = 0x7f7f7f7f;
	md = cn = 0;
	dfs(x,y);
	if(md > cn || !flag) {
		flag = 0;return 0;
	}
	if(md == cn) return 0;
	if(md < cn) return mi;
	return 0;
}
int main() {
	freopen("t-covering.in","r",stdin);
	freopen("t-covering.out","w",stdout);
	n = read(); m = read();
	for(int i = 1;i <= n;i ++) {
		a[i].push_back(0);
		for(int j = 1;j <= m;j ++) {
			a[i].push_back(read());
		} 
	}
	k = read();
	for(int i = 1;i <= k;i ++) {
		s = p[i].x = read()+1;o = p[i].y = read()+1;
		c[cg(s,o)] ++; d[cg(s,o)] ++;
		if(s > 1) c[cg(s-1,o)] ++;
		if(s < n) c[cg(s+1,o)] ++;
		if(o > 1) c[cg(s,o-1)] ++;
		if(o < m) c[cg(s,o+1)] ++;
	}
	LL ans = 0;
	flag = 1;
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= m;j ++) {
			if(c[cg(i,j)]) {
				ans += a[i][j];
			}
			if(d[cg(i,j)] && !f[cg(i,j)]) {
				ans -= solve(i,j);
			}
			if(!flag) {
				printf("No\n");
				return 0;
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值