【带权并查集维护相对关系】三道带权UFS基础题

【Up主又更新了个说..】

【UFS,Union_Find_Set】

说到这个带权并查集,我真的是第一次接触这种维护相对关系类型的。

所以各种不会,各种被虐,各种欲哭无泪。TAT若菜的烦恼你们会懂?

算了不说了...满纸荒唐言,一把辛酸泪....我发现我真的是个NC完全问题..NC到一种境界了。

直接上代码吧。我也不知道具体该说些什么。以后想补或者有人问(估计没人问,都是因为我太弱了...)再补上题解吧。


题目1:

 Lyp的战斗记录
                  lyp.pas/lyp.in/lyp.out
   众所周知,我们的lyp神犇外号叫Altman,的确,在另一个平行宇宙,lyp神犇就是一个——Altman。
     有一天,lyp神犇遇到了另一个平行宇宙中的他,得知了在其他的宇宙中,Altman是存在的,那么,怪兽也是存在的咯。
     作为一个有名的oier,lyp神犇想要统计一下各个宇宙中怪兽的战斗力,他发现,一些怪兽在不同的宇宙都出现过(!. !,难不成怪兽不穿越,算了,不管了),每一个Altman都提供给lyp一些信息,告诉他一个怪兽比另一个怪兽的战斗力大多少(这个数可以是负数)。
     Altman神犇为了给你以表现机会,将这些信息都给你,并会时不时地问你两个怪兽之间谁更强。
     假设Altman们提到了怪兽都按1~n编号。
     Altman们的信息的格式是 X  A  B  C  
     其中x固定为1,A,B是两个1~n的整数,表示怪兽编号,A怪兽的战斗力比B怪兽的战斗力大C(C为整数),保证信息之间不矛盾。
   Lyp的询问的格式是:Y  A  B
      Y 固定为2,A,B是怪兽编号,属于1~n,如果A比B 强,输出A;如果B比A强,输出B,如果无法判断或怪兽们一样强,输出0
 
【输入格式】: 第1行,两个数n,q,n表示怪兽总数,q表示询问与信息的总数;
              第2~q+1 行, 每一行是一个信息或询问,格式如题;
【输出格式】: 对于每一个询问,用单独一行回答。
【样例】:input   3  3
                1  1  2  3
                1  3  2  1
                2  1  3
      
Output   1
       

【数据约定】:  30%   n<=100  q<=300
               100%  n<=30000  q<=50000  	c的绝对值不超过5000

这道题注意一点:

我是第一次写非递归并查集,然后犯了这么一个错误:

for (; k != ff; ww -= w[k], k = f[k]) f[k] = ff, w[k] = ww;

这个实际上效率不高,因为相当于只把k连到了代表元上。要真真实现压缩路径,要这么写。

while (k != ff) t = f[k], f[k] = ff, u = w[k], w[k] = ww, ww -= u, k = t;

Code:

#include<iostream>
#include<cstdio> 
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<climits>
#define ot "%d"
#ifdef WIN32
#define otl "%I64d"
#else
#define otl "%lld"
#endif
//#define link(a, b) ({nt[++tot] = h[a]; t[h[a] = tot] = b; nt[++tot] = h[b]; t[h[b] = tot] = a;})
#define Max(t, a, b) ({t _ = (a), __ = (b); _ > __ ? _ : __;})
#define Min(t, a, b) ({t _ = (a), __ = (b); _ < __ ? _ : __;})
#define swap(t, a, b) ({t _ = (a); (a) = (b); (b) = _;})
#define maxn 30005

using namespace std;

int f[maxn], w[maxn], n, q;

void init()
{
	freopen("lyp.in", "r", stdin);
	freopen("lyp.out", "w", stdout);
	scanf(ot ot, &n, &q);
	for (int i = 1; i <= n; ++i) f[i] = i;
	memset(w, 0, sizeof w);
}

int find(int k)
{
	int ff = k, ww = 0, t, u;
	while (f[ff] != ff) ww += w[ff], ff = f[ff];
	while (k != ff) t = f[k], f[k] = ff, u = w[k], w[k] = ww, ww -= u, k = t;
	//for (; k != ff; ww -= w[k], k = f[k]) f[k] = ff, w[k] = ww;      Wrong!!!
	return ff;
}

int main()
{
	init();
	int ask, x, y, z, fx, fy;
	for (int i = 1; i <= q; ++i)
	{
		scanf(ot ot ot, &ask, &x, &y);
		fx = find(x), fy = find(y);
		if (ask == 1)
		{
			scanf(ot, &z);
			if (fx != fy) w[fx] = z + w[y] - w[x], f[fx] = fy;
		}
		else if(ask == 2)
		{
			if (fx != fy || w[x] == w[y]) printf("0");
				else if (w[x] > w[y]) printf(ot, x);
					else printf(ot, y);
			printf("\n");
		}
	}
	return 0;
}


题目2

输入

第一行一个数n表示序列长度。

接下来两行每行n个数,表示一个序列。

要求交换这两个序列中的某些位置相同的数字,使得两个序列中均无重复数字。输出最少交换次数。

数据保证有解。

code

#include<iostream>
#include<cstdio> 
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<climits>
#define ot "%d"
#define max(a, b) ({int _ = (a), __ = (b); _ > __ ? _ : __;})

using namespace std;

int n, a, cnt[2][100005], t[2][2][100005], num[100005], f[100005], w[100005], mx;

void init()
{
	freopen("dwu.in", "r", stdin);
	freopen("dwu.out", "w", stdout);
	scanf(ot, &n);
	mx = -INT_MAX;
	memset(num, 0, sizeof num);
	for (int p = 0; p <= 1; ++p)
		for (int i = 1; i <= n; ++i)
			scanf(ot, &a), t[num[a]][0][a] = p, t[num[a]++][1][a] = i, mx = max(mx, a);
}

int find(int k)
{
	int ww = 0, ff = k, t, cw;
	while (f[ff] != ff) ww ^= w[ff], ff = f[ff];
	while (f[k] != k)
		cw = w[k], w[k] = ww, ww ^= cw, t = f[k], f[k] = ff, k = t;
	return ff;
}

int main()
{
	init();
	for (int i = 1; i <= n; ++i) f[i] = i;
	int fx, fy, x, y;
	for (int i = 1; i <= mx; ++i)
		if (num[i] == 2)
		{
			x = t[0][1][i]; y = t[1][1][i];
			fx = find(x); fy = find(y);
			if (fx != fy) w[fx] = (int)(t[0][0][i] == t[1][0][i]) ^ w[y] ^ w[x], f[fx] = fy;
		}
	for (int i = 1; i <= n; ++i) if (f[i] != i) find(i), ++cnt[w[i]][f[i]]; else ++cnt[0][f[i]];
	int ans = 0;
	for (int i = 1; i <= n; ++i) if (f[i] == i) ans += min(cnt[0][i], cnt[1][i]);
	printf(ot, ans);
	return 0;
}

3.POJ1733

给定未知的01串,每次描述从x到y位中1的个数为奇数还是偶数

问多少次后矛盾。

TAT空间开小了调试了半天...

#include<iostream>
#include<cstdio> 
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<climits>
#define swap(a, b) ({int _ = a; a = b; b = _;})
#define ot "%d"
#define maxn 100005

using namespace std;

int n, len, f[maxn << 3], w[maxn << 3], q[3][maxn << 3], d[3][maxn << 3], v[maxn << 3], c;

void sort(int l, int r)
{
	int i = l, j = r, x = v[(l + r) >> 1];
	while (i <= j)
	{
		while (v[i] < x) ++i;
		while (v[j] > x) --j;
		if (i <= j)
		{
			swap(v[i], v[j]);
			swap(d[0][i], d[0][j]);
			swap(d[1][i], d[1][j]);
			swap(d[2][i], d[2][j]);
			++i, --j;
		}
	}
	if (i < r) sort(i, r);
	if (l < j) sort(l, j);
}

void init()
{
	freopen("shame.in", "r", stdin);
	freopen("shame.out", "w", stdout);
	scanf(ot ot "\n", &n, &len);
	char ch[10];
	int nn = n << 1;
	for (int i = 1; i <= (nn << 2) + 1; ++i) f[i] = i;
	for (int i = 1, j = 1; i <= nn; i += 2, ++j)
	{
		scanf("%d %d %s\n", &v[i], &v[i + 1], ch);
		d[0][i] = 0, d[1][i] = j, d[2][i] = (int)(ch[0] == 'o');
		d[0][i + 1] = 1, d[1][i + 1] = j, d[2][i + 1] = (int)(ch[0] == 'o');
	}
	sort(1, nn);
	int t = 0;
	for (int i = 1; i <= nn; ++i)
	{
		if (v[i] != v[i - 1]) ++t;
		q[d[0][i]][d[1][i]] = t << 1, q[2][d[1][i]] = d[2][i];
	}
}

int find(int k)
{
	int ff = k, ww = 0, t, cw;
	while (f[ff] != ff) ww ^= w[ff], ff = f[ff];
	while (k != ff) cw = w[k], w[k] = ww, ww ^= cw, t = f[k], f[k] = ff, k = t;
	return ff;
}

int main()
{
	init();
	int x, y, fx, fy, flag = 0;
	memset(w, 0, sizeof w);
	for (int i = 1; i <= n; ++i)
	{
		c = q[2][i], x = q[0][i], y = q[1][i] + 1;
		fx = find(x), fy = find(y);
		if (fx != fy)
		{
			f[fx] = fy;
			w[fx] = w[x] ^ w[y] ^ c;
		}
		else
			if (w[x] ^ w[y] != c)
			{
				printf(ot, i);
				flag = 1;
				break;
			}
	}
	if (!flag) printf("-1");
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值