P1173 [NOI2016] 网格

本文探讨了一种特殊布局下的图论问题,通过分析跳蚤和蛐蛐在棋盘上的分布,研究如何最少地替换跳蚤以打破原有的连通性。文章详细介绍了问题背景、解决思路、关键算法步骤及代码实现。

题目

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

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

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

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

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

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

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

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

输入格式

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

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

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

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

输出格式

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

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

输入 #1             输出 #1

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

 

一个显而易见的性质是答案不大于2(角上的跳蚤最多与两个相邻)

继而发现答案其实只可能是-1,0,1,2~~(废话)~~,分类讨论:

-1:nm-c<2nm−c<2或者nm-c=2nm−c=2且剩下的两只跳蚤相邻;

0:原图不连通;

1:原图联通且有割顶;

否则为2。

但n,m\le 10^9n,m≤109这种数据范围肯定不能直接做了,发现只有蚱蜢周围的跳蚤可能是割顶,是不是可以把它们选出来建图呢?

中间的*成为假的割顶。因此,我们要向外扩展两圈,然后如果在第一圈内发现割顶,那么才真正发现了割顶。这个另外的题解也有讲,这里就不仔细说了。

但是判联通性也有许多大坑,但许多题解都没提,我来提一下或许是我太蒟了

首先是怎么判。不能直接在建好的图上判,举个栗子:

会被判为不连通。而真正的不连通具有什么性质呢?我们发现,障碍点(蚱蜢)会把它周围的点分成至少两个联通块,由此得到算法1:

对选出的结点进行四联通floodfill,检查每个障碍周围的点是否均属于同一个联通块。

这个方法已经可以通过官方数据了~~,但我们还有万恶的UOJ~~

我们发现这个方法是可以hack的:

每个结点周围只有一个联通块,联通,~~于是你WA的一声哭了出来,~~所以,我们必须将连在一起的障碍一起处理,得到算法2:

对选出的结点进行四联通floodfill,对障碍进行八连通floodfill,检查每个障碍块周围的点是否均属于同一个联通块。

注意,对结点的检查不能判重

如果先检查了下面的#,再在检查上面的障碍块使省略重复访问,那么将返回联通。

这就没问题了。

代码:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
const int P=1000117;
const int N=100050;
char rB[1<<21],*rS,*rT;
inline char gc(){return rS==rT&&(rT=(rS=rB)+fread(rB,1,1<<21,stdin),rS==rT)?EOF:*rS++;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
int x[N],y[N],G[N*24],to[N*192],nxt[N*192],sz,cnt,pre[N*24],dfsc,n,m,c,tmpx[N*24],tmpy[N*24],ctmp;
bool isok[N*24],iscut[N*24];
struct node{
	int x,y;
	node(){}
	node(int x,int y):x(x),y(y){}
};
queue<node> Q,q;
struct Hash{  //用hash实现map
	int h[P],vx[N*25],vy[N*25],p[N*25],nxt[N*25],sz;
	inline void clear(){
		memset(h,0,sizeof(h));sz=0;
	}
	inline void ins(int x,int y,int id){
		int pos=((ll)(x-1)*n+y-1)%P;
		vx[++sz]=x;vy[sz]=y;p[sz]=id;nxt[sz]=h[pos];h[pos]=sz;
	}
	inline int ask(int x,int y){
		for(int k=h[((ll)(x-1)*n+y-1)%P];k;k=nxt[k])if(vx[k]==x&&vy[k]==y)return p[k];
		return 0;
	}
}h,col,tem;
inline int Abs(int x){return x<0?-x:x;}
inline int Max(int a,int b){return a>b?a:b;}
inline void add(int u,int v){
	to[++sz]=v;nxt[sz]=G[u];G[u]=sz;
	to[++sz]=u;nxt[sz]=G[v];G[v]=sz;
}
inline bool check(){
	int i,j,k,tx,ty;
	for(i=1;i<=n;++i)
		for(j=1;j<=m;++j)if(!h.ask(i,j)){
			for(k=0;k<4;++k)if((tx=i+dx[k])&&tx<=n&&(ty=j+dy[k])&&ty<=m&&!h.ask(tx,ty))return 1;
			return 0;
		}
}
inline void bfs(int sx,int sy,int cl){  //第一次floodfill
	int i,u,v,tx,ty;
	q.push(node(sx,sy));col.ins(sx,sy,cl);
	while(!q.empty()){
		u=q.front().x;v=q.front().y;q.pop();
		for(i=0;i<4;++i)if((tx=u+dx[i])&&tx<=n&&(ty=v+dy[i])&&ty<=m&&h.ask(tx,ty)>0&&!col.ask(tx,ty)){
			col.ins(tx,ty,cl);  //用col来记录所属联通块编号(也成为颜色)
			q.push(node(tx,ty));
		}
	}
}
inline bool bfs2(int sx,int sy){
	int i,u,v,x,y,t;
	q.push(node(sx,sy));tem.ins(sx,sy,-1);
	while(!q.empty()){
		u=q.front().x;v=q.front().y;q.pop();
		for(x=Max(1,u-1);x<=n&&x<=u+1;++x)
			for(y=Max(1,v-1);y<=m&&y<=v+1;++y)if((t=h.ask(x,y))&&!tem.ask(x,y))if(t==-1){
				tem.ins(x,y,-1);  //用tem来防止对障碍结点重复访问
//对跳蚤结点的重复访问最多总共c*8个,不会影响复杂度
				q.push(node(x,y));
			}else{tmpx[++ctmp]=x;tmpy[ctmp]=y;}
	}
	if(ctmp==-1)return 1;
	for(i=1,t=col.ask(tmpx[0],tmpy[0]);i<=ctmp;++i)if(col.ask(tmpx[i],tmpy[i])!=t)return 0;
	return 1;
}
inline bool ncon(){  //判断是否不连通
	int i,u,v,ccl=0;
	col.clear();
	while(!Q.empty()){
		u=Q.front().x;v=Q.front().y;Q.pop();
		if(col.ask(u,v))continue;
		bfs(u,v,++ccl);
	}
	tem.clear();
	for(i=0;i<c;++i)if(!tem.ask(x[i],y[i])){
		ctmp=-1;
		if(!bfs2(x[i],y[i]))return 1;
	}
	return 0;
}
int dfs(int u,int fa){  //dfs求割顶
	int i,v,lowu=pre[u]=++dfsc,lowv,chd=0;
	for(i=G[u];i;i=nxt[i])if((v=to[i])!=fa)if(!pre[v]){
		++chd;
		if((lowv=dfs(v,u))>=pre[u])iscut[u]=1;
		if(lowv<lowu)lowu=lowv;
	}else if(pre[v]<lowu)lowu=pre[v];
	if(!fa&&chd==1)iscut[u]=0;
	return lowu;
}
int main(){
	int T=rd(),i,j,k,l,t,tt,tx,ty;
	bool ok;
	while(T--){
		n=rd();m=rd();c=rd();
		h.clear();
		for(i=0;i<c;++i){
			x[i]=rd();y[i]=rd();
			h.ins(x[i],y[i],-1);
		}
		if((ll)n*m-c<2ll){
			puts("-1");
			continue;
		}
		if((ll)n*m-c==2ll){
			puts(check()?"-1":"0");
			continue;
		}
		memset(G,0,sizeof(G));ok=sz=cnt=dfsc=0;
		memset(pre,0,sizeof(pre));
		memset(iscut,0,sizeof(iscut));
		memset(isok,0,sizeof(isok));
        //建图
		for(i=0;i<c;++i)
			for(j=Max(1,x[i]-2);j<=x[i]+2&&j<=n;++j)
				for(k=Max(1,y[i]-2);k<=y[i]+2&&k<=m;++k)if(!(t=h.ask(j,k))){
					h.ins(j,k,++cnt);Q.push(node(j,k));
					isok[cnt]=Max(Abs(j-x[i]),Abs(k-y[i]))<=1;
					for(l=0;l<4;++l)if((tx=j+dx[l])&&tx<=n&&(ty=k+dy[l])&&ty<=m&&(tt=h.ask(tx,ty))>0)add(cnt,tt);
				}else if(t>0&&Max(Abs(j-x[i]),Abs(k-y[i]))<=1)isok[t]=1;
		if(ncon()){
			puts("0");
			continue;
		}
		if(n==1||m==1){  //一行或一列可以特判
			puts("1");
			continue;
		}
		for(i=1;i<=cnt;++i){
			if(!pre[i])dfs(i,0);
			if(isok[i]&&iscut[i]){
				puts("1");
				ok=1;break;
			}
		}
		if(!ok)puts("2");
	}
	return 0;
}

<think>好的,用户需要查找洛谷P1173 NOI2016网格问题的Python解决方案或代码实现。首先,我需要回忆一下这个题目的具体要求。题目大意是判断一个网格是否会被障碍物分成不连通的部分,这可能涉及到图论中的割点或割边判断。 接下来,我需要考虑如何用Python实现这个问题的解法。这类题目通常需要高效的算法,因为数据规模可能较大。Python在处理大数据时可能会遇到性能问题,所以需要考虑优化方法。 首先,题目中的网格可以看作是一个图,其中某些格子被障碍物占据。判断是否存在割点,即移除某个点后图是否不连通,这是关键。Tarjan算法通常用于寻找割点,但实现起来需要注意细节,尤其是用Python时递归深度可能受限,可能需要用迭代方式或者优化代码。 然后,我需要查阅是否有现有的Python解法。不过用户提到站内引用中的内容似乎与快速排序或其他问题相关,可能不直接适用。但根据引用[2],用户提到了代码优化和UB(未定义行为)的问题,这提示在编写Python代码时需要注意避免类似错误,比如确保所有分支都有返回值,避免无限循环等。 另外,用户可能需要的是具体的代码示例。考虑到Python在二维网格问题中的处理,通常会用坐标转换、邻接表或矩阵来表示图结构。对于割点检测,可以使用Tarjan算法,维护每个节点的dfn(发现时间)和low(能回溯到的最早节点)值,判断是否存在子节点无法通过其他路径回到祖先节点。 不过,Python的递归深度限制可能对Tarjan算法产生影响,尤其是在大网格情况下。因此,可能需要用非递归的DFS实现,或者调整递归深度限制。但这也可能导致代码复杂度增加。 另外,题目中的障碍物可能形成多个连通块,需要处理多个情况。比如,当障碍物数量较少时,可能直接判断是否形成包围圈;当障碍物较多时,需要更复杂的判断逻辑。 总结步骤可能包括: 1. 将网格转换为图结构,标记障碍物。 2. 使用Tarjan算法寻找割点。 3. 判断是否存在割点导致图不连通。 4. 处理特殊情况,如网格过小、障碍物数量不足等。 在编写Python代码时,需要注意数据结构的效率,比如使用集合或字典来快速访问节点,避免重复计算。此外,可能的优化包括剪枝、预处理等。 最后,根据用户提供的引用,虽然引用内容不直接相关,但需要注意代码的正确性和优化,避免出现未定义行为或逻辑错误。例如,确保所有函数都有正确的返回语句,避免因编译器优化导致的错误,这在Python中可能表现为逻辑错误而非运行错误。</think>关于洛谷 P1173 NOI2016 网格问题的 Python 解法,该问题属于图论中的连通性判断问题,需要判断是否存在障碍物形成的割点导致网格不连通。以下是解题思路和实现要点: --- ### **问题核心分析** 题目要求判断 $n \times m$ 网格中,给定 $c$ 个障碍物的位置后,是否会将网格分割为多个不连通区域。关键在于判断是否存在 **割点**(即移除该点会导致图不连通)或 **割边**。 --- ### **算法选择** 1. **Tarjan 算法**:用于寻找无向图的割点。若存在割点,则网格可能被分割。 2. **连通性检测**:需考虑障碍物分布对整体连通性的影响,特别是边界情况和稀疏障碍物场景[^1]。 --- ### **Python 实现步骤** #### 1. **坐标离散化与邻接表构建** 将网格坐标转换为图中的节点,并为每个非障碍物节点建立邻接关系: ```python def build_graph(n, m, obstacles): obstacles_set = set(obstacles) graph = defaultdict(list) directions = [(-1,0), (1,0), (0,-1), (0,1)] for x in range(n): for y in range(m): if (x, y) not in obstacles_set: for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and (nx, ny) not in obstacles_set: graph[(x, y)].append((nx, ny)) return graph ``` #### 2. **Tarjan 算法实现割点检测** 通过深度优先搜索(DFS)计算每个节点的 `dfn`(发现时间)和 `low`(回溯值): ```python def find_cut_points(graph, start): visited = set() dfn, low = {}, {} parent = {} cut_points = set() time = 0 def tarjan(u): nonlocal time dfn[u] = low[u] = time time += 1 children = 0 for v in graph[u]: if v not in dfn: parent[v] = u children += 1 tarjan(v) low[u] = min(low[u], low[v]) if parent.get(u) is None and children > 1: cut_points.add(u) elif parent.get(u) is not None and low[v] >= dfn[u]: cut_points.add(u) elif v != parent.get(u): low[u] = min(low[u], dfn[v]) tarjan(start) return cut_points ``` #### 3. **特殊情况处理** - **极小网格**:如 $n \times m \leq 2$ 时无需判断。 - **障碍物数量不足**:若障碍物数量少于 2,则无法分割网格[^2]。 --- ### **完整代码框架** ```python from collections import defaultdict def main(): n, m, c = map(int, input().split()) obstacles = [tuple(map(int, input().split())) for _ in range(c)] if n * m - c < 2: print("No") return if c == 0: print("Yes" if n * m >= 2 else "No") return graph = build_graph(n, m, obstacles) nodes = list(graph.keys()) if not nodes: print("No") return cut_points = find_cut_points(graph, nodes[0]) print("Yes" if len(cut_points) > 0 else "No") if __name__ == "__main__": main() ``` --- ### **注意事项** 1. **性能优化**:Python 递归深度有限,对大规模网格需改用迭代式 DFS。 2. **边界条件**:注意坐标范围和障碍物去重。 3. **连通性验证**:需确保所有非障碍物节点均被访问,防止漏判。 --- 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值