便(带权并查集)( AT2033 マス目と整数 / Grid and Integers)

本文介绍了如何使用带权并查集解决一个棋盘填充问题,其中涉及行与列之间的权值差约束。通过按行排序并处理列的权值差,检查并确保满足条件。如果存在唯一的确定值且大于零,则解决方案是合法的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【输出格式】
T行.第i行是第i组数据的答案.有合法方案时输出一行Yes,没有时输出一行No.

6
2 2 3
1 1 0
1 2 10
2 1 20
2 3 5
1 1 0
1 2 10
1 3 20
2 1 30
2 3 40
2 2 3
1 1 20
1 2 10
2 1 0
3 3 4
1 1 0
1 3 10
3 1 10
3 3 20
2 2 4
1 1 0
1 2 10
2 1 30
2 2 20
1 1 1
1 1 -1

如下图是一个2*2的正方形。

a1 + a4 = a3 + a2;

a1 - a2 = a3 - a4;

a1 - a3  = a2 - a4;

不同行相同两列之差相同,不同列相同行之差相同。

我们可以暴力记录差。。。。然而最后一组显然过不了=。=//我就是暴力记录的。。。

所以我们可以考虑并查集,带权并查集。//除了维护父亲,还要维护和父亲的权值差。

x代表行,y代表列。

按x排序,x相同的,y不同的两个点,

如果y1,y2不在同一个并查集,那就把fa[y1]设为y2,并把y1和它父亲y2的差设为两个点的权值差。//表示y1列和y2列有固定的差了。

如果y1,y2在同一个并查集,说明y1列和y2列已经有固定的差了。我们检验这两个点的权值差等不等于固定的差。不等于就不满足条件。

处理行之差同理。

如果以上满足,这样可以处理出一个已填位置合法的棋盘。

然后因为不同行相同两列之差相同,不同列相同行之差相同,所以有一些未填的位置,只有唯一确定的值。

我们要找到最小的唯一确定的值(对于每个并查集都有一个),看它是否大于零,大于零合法,否则不合法。

枚举已知位置,找行父亲或者列父亲都行,只用找一个就好因为棋盘能够确定的值是唯一确定的。假设找行父亲。

根据它这行和它父亲这行一定存在一列有一个固定的差,通过这个固定的差来找到它父亲的最小值。

它父亲和其他行(其他儿子)也存在一个固定的差,使这个固定的差最小,来找到最小的唯一确定的值。

#include<bits/stdc++.h>
using namespace std;
int t,r,c,n,fa1[100005],fa2[100005],cha1[100005],cha2[100005],min1[100005],min2[100005];
void read(int &x)
{
	x = 0;  int f = 0;  char c = getchar();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = 1; 	c  = getchar();
	}
	while(c >= '0' && c <= '9')
	{
		x = x * 10 + c - '0'; c = getchar();
	}
	if(f) x = -x ;
}
struct node
{
	int x,y,val;
};
node q[100005];
bool cmp1(const node a,const node b)
{
	return a.x < b.x;
}
bool cmp2(const node a,const node b)
{
	return a.y < b.y;
}
int find1(int x)
{
	if(fa1[x] == x) return x;
	int ha  = find1(fa1[x]);
	cha1[x] += cha1[fa1[x]];
	return fa1[x] =  ha;
}
int pd1(int a,int b)
{
	int r1 = find1(q[a].y);  int r2 = find1(q[b].y);
	if(r1 != r2)
	{
		int val1 = q[a].val - cha1[q[a].y];
		int val2 = q[b].val - cha1[q[b].y] ;
		fa1[r1] = r2;
		cha1[r1] = val1 - val2;	return  0;
	}
	else return q[a].val - cha1[q[a].y] == q[b].val - cha1[q[b].y] ?  0 : 1;
}
int find2(int x)
{
	if(fa2[x] == x) return x;
	int ha  = find2(fa2[x]);
	cha2[x] += cha2[fa2[x]];
	return fa2[x] =  ha;
}
int pd2(int a,int b)
{
	int r1 = find2(q[a].x);  int r2 = find2(q[b].x);
	if(r1 != r2)
	{
		int val1 = q[a].val - cha2[q[a].x];
		int val2 = q[b].val - cha2[q[b].x] ;
		fa2[r1] = r2;
		cha2[r1] = val1 - val2;
		return  0;
	}
	else return q[a].val - cha2[q[a].x] == q[b].val - cha2[q[b].x] ?  0 : 1;
}
int main()
{
	freopen("then10.in","r",stdin);
       freopen("then.out","w",stdout);
    read(t);
	for(int e =1 ; e <= t; e++)
	{
		if(e == 3)
		int made = 0;
		memset(min1,0x3f3f3f3f,sizeof(min1));
		memset(min2,0x3f3f3f3f,sizeof(min2));
		int ha = 0;
		read(r);read(c);read(n);
	   for(int i = 1; i <= c; i++)
	    {
	   	 fa1[i] = i; cha1[i] = 0;
	   }
	    for(int i = 1; i <= r; i++)
	    {
	   	 fa2[i] = i; cha2[i] = 0;
	   }
	   for(int i = 1; i <= n; i++)
	    {
	    	read(q[i].x);read(q[i].y);read(q[i].val);
	       if(q[i].val < 0) ha = 1;
	    }
	    if(ha == 1) {
	    printf("No\n");	 continue;
	    }
		sort(q+1,q+n+1,cmp1);
       for(int i = 1; i < n; i++)
       {
       	if(q[i].x == q[i+1].x)
       	{
          if(pd1(i,i+1)) ha = 1;
       	}
       }//处理相同行,不同列,也就是不同列之间的固定差
        if(ha == 1) {
	    printf("No\n");	 continue;
	    }
        sort(q+1,q+n+1,cmp2);
       for(int i = 1; i < n; i++)
       {
       	if(q[i].y == q[i+1].y)
       	{
          if(pd2(i,i+1)) ha = 1;
       	}
       }//处理相同列,不同行,也就是不同行之间的固定差
        if(ha == 1) {
	    printf("No\n");	 continue;
	    }
       for(int i = 1; i <= n; i++)
       {
       	int rt = find1(q[i].y);
       	min1[rt] = min(min1[rt],q[i].val - cha1[q[i].y]);
       }
       for(int i = 1; i <= c; i++)
	   {
	   	int rt = find1(i);
	   	    min2[rt] = min(min2[rt],cha1[i]);
	   }//找父亲的最小值
	   for(int i = 1; i <= c; i++)
	    {
	    	int rt = find1(i);
	    	if(rt == i) 
			if(min2[rt] + min1[rt] < 0) ha = 1; 
	    }//找其他行的最小值,也就是唯一确定的最小值
	    if(ha == 1) {
	    printf("No\n");	 continue;
	    } 
	      printf("Yes\n");	 
	}
	  return 0;	
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值