洛谷 P1173 [NOI2016] 网格

题目描述

跳蚤国王和蛐蛐国王在玩一个游戏。

他们在一个 �n 行 �m 列的网格上排兵布阵。其中的 �c 个格子中 (0≤�≤�⋅�)(0≤c≤n⋅m),每个格子有一只蛐蛐,其余的格子中,每个格子有一只跳蚤。

我们称占据的格子有公共边的两只跳蚤是相邻的。

我们称两只跳蚤是连通的,当且仅当这两只跳蚤相邻,或存在另一只跳蚤与这两只跳蚤都连通。

现在,蛐蛐国王希望,将某些(零个,一个或多个)跳蚤替换成蛐蛐,使得在此之后存在至少两只跳蚤不连通。

例如:图 11 描述了一个 �=4n=4,�=4m=4,�=2c=2 的情况。

这种情况下蛐蛐国王可以通过将第二行第二列,和第三行第三列的两只跳蚤替换为蛐蛐,从而达成他的希望,如右图所示。并且,不存在更优的方案,但是可能存在其他替换两只跳蚤的方案。

你需要首先判断蛐蛐国王的希望能否被达成。如果能够达成,你还需要最小化被替换的跳蚤的个数。

输入格式

每个输入文件包含多组数据。

输入文件的第一行只有一个整数 �T,表示数据的组数。

接下来依次输入 �T 组数据,每组数据的第一行包含三个整数 �,�,�n,m,c。

接下来 �c 行,每行包含两个整数 �,�x,y 表示第 �x 行,第 �y 列的格子被一个蛐蛐占据。每一组数据当中,同一个蛐蛐不会被多次描述。

输出格式

对于每一组数据依次输出一行答案。

如果这组数据中,蛐蛐国王的希望不能被达成,输出 −1−1。否则,输出被替换的跳蚤的个数的最小值。

输入输出样例

输入 #1复制

4
4 4 2
1 1
4 4
2 3 1
1 2
2 2 2
1 1
2 2
1 1 0

输出 #1复制

2
1
0
-1

说明/提示

样例解释

第一组数据就是问题描述中的例子。

对于第二组数据,可以将第二行第二列的一只跳蚤替换为蛐蛐,从而使得存在两只跳蚤不连通,并且不存在更优的方案。

对于第三组数据,最初已经存在两只跳蚤不连通,故不需要再进行替换。

对于第四组数据,由于最多只有一只跳蚤,所以无论如何替换都不能存在两只跳蚤不连通。

数据范围

对于全部的测试点,保证 1≤�≤201≤T≤20。我们记 ∑�∑c 为某个测试点中,其 �T 组输入数据的所有 �c 的总和。对于所有的测试点,∑�≤105∑c≤105。

对于全部的数据,满足 1≤�,�≤1091≤n,m≤109,0≤�≤�×�0≤c≤n×m,1≤�≤�,1≤�≤�1≤x≤n,1≤y≤m。

每个测试点的详细数据范围见下表。表中的 �,�,�n,m,c 均是对于单个输入数据(而非测试点)而言的,也就是说同一个测试点下的 �T 组数据均满足限制条件;而 ∑�∑c是对于单个测试点而言的。为了方便阅读,“测试点”一列被放到了表格的中间而不是左边。

�,�n,m测试点�c
�∗�≤4n∗m≤411�≤�∗�c≤n∗m
�∗�≤8n∗m≤822�≤�∗�c≤n∗m
�∗�≤15n∗m≤1533�≤�∗�c≤n∗m
�∗�≤30n∗m≤3044�≤�∗�c≤n∗m
�∗�≤100n∗m≤10055�≤�∗�c≤n∗m
�∗�≤300n∗m≤30066�≤�∗�c≤n∗m
�∗�≤103n∗m≤10377�≤�∗�c≤n∗m
�∗�≤2×104n∗m≤2×10488�≤5c≤5
�∗�≤2×104n∗m≤2×10499�≤15c≤15
�∗�≤2×104n∗m≤2×1041010�≤30c≤30
�,�≤2×104,�∗�≤2×104n,m≤2×104,n∗m≤2×1041111∑�≤2×104∑c≤2×104
�,�≤2×104,�∗�≤105n,m≤2×104,n∗m≤1051212∑�≤2×104∑c≤2×104
�,�≤2×104,�∗�≤3×105n,m≤2×104,n∗m≤3×1051313∑�≤2×104∑c≤2×104
�,�≤2×104,�∗�≤106n,m≤2×104,n∗m≤1061414∑�≤2×104∑c≤2×104
�,�≤2×104,�∗�≤109n,m≤2×104,n∗m≤1091515∑�≤2×104∑c≤2×104
�,�≤105n,m≤1051616∑�≤105∑c≤105
�,�≤109n,m≤1091717�=0c=0
�,�≤109n,m≤1091818�≤1c≤1
�,�≤109n,m≤1091919�≤2c≤2
�,�≤109n,m≤1092020�≤3c≤3
�,�≤109n,m≤1092121�≤10c≤10
�,�≤109n,m≤1092222�≤30c≤30
�,�≤109n,m≤1092323�≤300c≤300
�,�≤109n,m≤1092424∑�≤2×104∑c≤2×104
�,�≤109n,m≤1092525∑�≤105∑c≤105

将有蛐蛐的看做黑点,有跳蚤的看做白点。

显然答案只可以是无解或者 0,1,20,1,2。

  • 若所有四联通的白色格子组成的图不连通,则答案是 00。

  • 否则若图有割点,则答案为 11

  • 否则若图只有不超过两个点,则无解

  • 否则为 22

前三个情况是显然的,最后一种情况可以分图有 33 个点和超过 33 个分别证明。

于是直接建图跑 tarjan 即可 �(��)O(nm)。

然而这张图点很多但是空位很少,考虑将其缩成一个点数边数均为 �(�)O(c) 的图使得缩完答案不变。

考虑只保留以下白点:

  • 与网格四个角的至少一个角的 �,�x,y 坐标之差 ≤2≤2

  • 与某个黑点八连通

  • 在网格的上边或下边上,且所在列有至少一个黑点

  • 在网格的左边或右边上,且所在行有至少一个黑点

然后对于所有剩下的白点,若两个白点点在同一行或者同一列,且中间没有其它点(包括黑点黑剩下的白点),就连一条边。

注意并不是只建四连通的边。

例如这样,蓝色的格子是保留下来的点。

然后发现这张图的答案与原图的答案大多数情况是一样的,但判 −1−1 的时候需要特判一下若图有两个点但这两个点虽然有边但不四连通的情况,这种情况实际上会有至少三个点在里面,答案是 11,或者可以判一下 �=1,�=1n=1,m=1 也可以。

而且考虑每个黑点只会最多贡献它周围 88 个,以及边上 44 个共 1212 个点,所以一共 �(�)O(c) 个点。

所以复杂度瓶颈在于建图,复杂度 Θ(�log⁡�)Θ(clogc)

代码如下:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXC=1e5,SIZE=MAXC*4*9;
const int dx[]={0,0,-1,-1,-1,1,1,1};
const int dy[]={-1,1,-1,0,1,-1,0,1};

struct Point
{
	int x,y;
	inline void Scan() {scanf("%d %d",&y,&x);}
	bool operator == (const Point &a) const
	{return x==a.x && y==a.y;}
	bool operator < (const Point &a) const
	{return x==a.x ? y<a.y : x<a.x;}
	bool operator > (const Point &a) const
	{return x==a.x ? y>a.y : x>a.x;} 
}ver[MAXC+5];

ll n,m;int C;
ll V,E,F;

int fa[SIZE+5],Size[SIZE+5],tot,Outside[SIZE+5];
inline void Clean() {tot=0;}
inline int New() {++tot,fa[tot]=tot,Size[tot]=1,Outside[tot]=0;return tot;}
int Find(int x) {return fa[x]==x ? x : Find(fa[x]);}
pair<int,int> Record[10];int rnum;
inline void Union(int a,int b)
{
	a=Find(a),b=Find(b);
	if(a==b) return;
	if(Size[a]<Size[b]) swap(a,b);
	Size[a]+=Size[b];
	fa[b]=a;
	Outside[a]+=Outside[b];
	
	Record[++rnum]=make_pair(a,b);
}
inline void Cut(int a,int b)
{
	Size[a]-=Size[b];
	fa[b]=b;
	Outside[a]-=Outside[b];
}

set<Point> mapn;//黑点矩阵
map<Point,int> Code;//黑点的角的编号 

inline bool OnEdge(int x,int y) {return x==1 || x==m+1 || y==1 || y==n+1;}

int Q[10],Tail;
inline void Insert(int x,int y)
{
	--V,mapn.insert(Point{x,y});
	
	if(x>1 && mapn.find(Point{x-1,y})==mapn.end()) --E;
	if(x<m && mapn.find(Point{x+1,y})==mapn.end()) --E;
	if(y>1 && mapn.find(Point{x,y-1})==mapn.end()) --E;
	if(y<n && mapn.find(Point{x,y+1})==mapn.end()) --E;
	
	if(Code.find(Point{x,y})==Code.end()) Code[Point{x,y}]=New(),Outside[tot]=OnEdge(x,y);
	if(Code.find(Point{x+1,y})==Code.end()) Code[Point{x+1,y}]=New(),Outside[tot]=OnEdge(x+1,y);
	if(Code.find(Point{x,y+1})==Code.end()) Code[Point{x,y+1}]=New(),Outside[tot]=OnEdge(x,y+1);
	if(Code.find(Point{x+1,y+1})==Code.end()) Code[Point{x+1,y+1}]=New(),Outside[tot]=OnEdge(x+1,y+1);
	Tail=rnum=0;
	Q[++Tail]=Find(Code[Point{x,y}]);
	Q[++Tail]=Find(Code[Point{x+1,y}]);
	Q[++Tail]=Find(Code[Point{x,y+1}]);
	Q[++Tail]=Find(Code[Point{x+1,y+1}]);
	sort(Q+1,Q+Tail+1),Tail=unique(Q+1,Q+Tail+1)-Q-1;
	int delta=0;
	for(int i=1;i<=Tail;i++) delta-=(!Outside[Q[i]]);
	for(int i=2;i<=Tail;i++) Union(Q[1],Q[i]);
	for(int i=1;i<=Tail;i++) Q[i]=Find(Q[i]);
	sort(Q+1,Q+Tail+1),Tail=unique(Q+1,Q+Tail+1)-Q-1;
	for(int i=1;i<=Tail;i++) delta+=(!Outside[Q[i]]);
	F+=delta;
}

inline ll Unicom() {return V-E+F;}

int main()
{
	int T;scanf("%d",&T);
	while(T--)
	{
		scanf("%lld %lld %d",&n,&m,&C);
		V=n*m,E=n*(m-1)+(n-1)*m,F=(n-1)*(m-1);
		Clean(),mapn.clear(),Code.clear();
		
		for(int i=1;i<=C;i++) ver[i].Scan(),Insert(ver[i].x,ver[i].y);
		if(Unicom()>1) {printf("0\n");continue;}
		if(!V || V==1 || (V==2 && Unicom()==1)) {printf("-1\n");continue;}
		if(n==1 || m==1) {printf("1\n");continue;}
		//只添一个黑格子
		bool OK=0;
		for(int i=1;i<=C;i++)
		{
			for(int j=0;j<8;j++)
			{
				int x=ver[i].x+dx[j],y=ver[i].y+dy[j];
				if(x<1 || m<x || y<1 || n<y) continue;
				if(mapn.find(Point{x,y})!=mapn.end()) continue;
				ll v=V,e=E,f=F;
				Insert(x,y);
				if(Unicom()>1) {OK=1;break;}
				//撤销 
				V=v,E=e,F=f;
				mapn.erase(Point{x,y});
				for(int k=rnum;k;k--) Cut(Record[k].first,Record[k].second);
			}
			if(OK) break;
		}
		if(OK) printf("1\n");
		else printf("2\n");
	}
	return 0;
}

 拜拜!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值