【uoj#142】【UER #5】万圣节的南瓜灯 乱搞+并查集

题目描述

给出一张 $n\times m$ 的网格图,两个格子之间有一条双向边,当且仅当它们相邻,即在网格图中有一条公共边。

特殊地,对于 $1\le x\le n​$ ,$(x,1)​$ 和 $(x,m)​$ 也视为相邻。但对于 $1\le y\le m​$ ,$(1,y)​$ 和 $(n,y)​$ 不视为相邻。

现在这张网格图有 $k​$ 个格子坏掉了,你需要判断剩下的部分是否形成一张无向无环连通图。

$n,m\le 10^9$ ,$k\le 10^5$ 。


题解

乱搞+并查集

对于剩下的图:点数为 $nm-k$ ,边数大于等于 $2nm-m-4k$ 。

由于剩下的部分是一棵树,因此有点数大于边数,即 $nm-k>2nm-m-4k$ 。

整理得 $(n-1)·m<3k$ 。

由于 $k$ 只有 $10^5$ ,因此 $(n-1)·m$ 只有 $3\times 10^5$ 。又因为 $n\le 3$ ,因此 $nm$ 也只有 $4.5\times 10^5$ 。

经过构造后得出最大的 $nm$ 在 $n=4,m=99999$ 时取到,为 $399996$ 。

因此当满足 $(n-1)·m<3k$ 时暴力(数组要开到 $449997$ 以上),否则输出No即可。时间复杂度 $O(449997T)$ 。

或当满足 $nm\le 400000$ 时暴力(数组要开到 $400000$ 以上),否则输出No即可。时间复杂度 $O(400000T)$ 。

#include <cstdio>
#include <cstring>
#define pos(i , j) ((i - 1) * m + j)
int v[450010] , f[450010];
int find(int x)
{
	return x == f[x] ? x : f[x] = find(f[x]);
}
int main()
{
	int T;
	scanf("%d" , &T);
	while(T -- )
	{
		memset(v , 0 , sizeof(v));
		int n , m , k , i , j , x , y , c = 0;
		scanf("%d%d%d" , &n , &m , &k);
		if(1ll * n * m >= m + 3 * k)
		{
			while(k -- ) scanf("%*d%*d");
			printf("No");
			if(T) puts("");
			continue;
		}
		for(i = 1 ; i <= k ; i ++ ) scanf("%d%d" , &x , &y) , v[pos(x , y)] = 1;
		for(i = 1 ; i <= n * m ; i ++ ) f[i] = i;
		for(i = 1 ; i <= n ; i ++ )
		{
			for(j = 1 ; j <= m ; j ++ )
			{
				if(!v[pos(i , j)])
				{
					if(i < n && !v[pos(i + 1 , j)]) f[find(pos(i , j))] = find(pos(i + 1 , j)) , c ++ ;
					if(!v[pos(i , j % m + 1)]) f[find(pos(i , j))] = find(pos(i , j % m + 1)) , c ++ ;
				}
			}
		}
		if(c != n * m - k - 1) printf("No");
		else
		{
			for(i = 1 ; i <= n * m ; i ++ )
				if(!v[i])
					x = find(i);
			for(i = 1 ; i <= n * m ; i ++ )
				if(!v[i] && find(i) != x)
					break;
			if(i <= n * m) printf("No");
			else printf("Yes");
		}
		if(T) puts("");
	}
	return 0;
}

 

posted @ 2018-03-31 11:18  GXZlegend  阅读(395)  评论(0编辑  收藏  举报