2024“钉耙编程”中国大学生算法设计超级联赛(2) 图计算

Problem - 7456 (hdu.edu.cn)

小 T 有一个 n 个点 m 条边的**无向图**,在这个图上他能够很轻易地计算有多少个点对互相联通。

有一天,调皮的小 F 将这张图复制了 d 次,也就是总共有 d+1 张图,每一张图初始都与小 T 原来持有的图相同。

小 F 为了难倒小 T,他还会偷偷往这些图里面加总共 k 条边。小 F 的每次加边操作会给定 (u,v,w) 表示用一条**无向边**连接第 w 张图上的 (u,v) 点对。每次操作后小 F 还是会问小 T 有多少个**无序点对** (u,v) 满足 u,v 在 d+1 张图上都**联通**,且 u≠v。

我们认为一个点对 (u,v) **联通**意味着 u 可以通过图上的一些边抵达 v,同样的 v 可以通过图上的一些边抵达 u。

调皮的小 F 难倒了小 T,你能编写程序帮帮小 T 吗?

Ei[x],为x在第i张图中所属的集合,

对于一个点对u,v在所有的图中都联通,等价于:

Ei[u]==Ei[v] for i ∈[1,d+1]

我们不妨将记: 

 h[u]=\sum_{i=1}^{d+1} hash(E_i[u]) 

对于两个点h[u]==h[v] 我们可以认为他们在每一个图中都属于一个集合。这样我们就可以维护每个点的h值,所以说,我们需要知道每个集合中到底有哪些点,我们用一个sons数组来记录集合中的点,在每次更新的时候,我们采用启发式合并,将小的集合合并进大的集合,这样可以保证每个点最多被合并log次,k次合并的总体复杂度就是(n*d*log(n*d))的

对于每次合并的时候,我们先把合并设涉及到的点的h值记录下来,在ans中刨除这部分h值的答案,

再更新每个点的h值,然后重新统计这些h值对答案的影响

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<algorithm> 
#include<string.h>
#include<iostream>
#include<queue>
#include<math.h>
#include<string>
#include<map>
#include<functional>
#include<unordered_map>
#include<bitset>
#include<random>
#include<set>

using namespace std;

#define int long long
#define inf 0x3f3f3f3f
#define N 50005
#define D 105
#define ull unsigned long long

int t;


int p[D][N], sz[D][N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量


vector<int>sons[D][N];//记录并查集的子节点

ull hs[D][N];//记录每个节点的哈希值

ull h[N];//记录每个点的所有父亲的哈希值的和,如果h[u]==h[v]则可以认为他们在所有的图上都在一个集合中

// 返回x的祖宗节点
int find(int w, int x)
{
	if (p[w][x] != x) p[w][x] = find(w, p[w][x]);
	return p[w][x];
}

int n, m, d, k;

void init()
{
	for (int i = 1; i <= n + 1; i++)
	{
		p[0][i] = i;
		sz[0][i] = 1;
		sons[0][i].push_back(i);
	}

}

void merge(int w,int u, int v)
{
	int fu = find(w, u);
	int fv = find(w, v);

	if (fu == fv)return;

	if (sz[fu] < sz[fv])
	{
		swap(u, v);
		swap(fu, fv);
	}

	for (auto it : sons[w][fv])
	{
		sons[w][fu].push_back(it);
	}

	sz[w][fu] += sz[w][fv];
	p[w][fv] = fu;


	sz[w][fv] = 0;
	sons[w][fv].clear();
	sons[w][fv].shrink_to_fit();
}

signed main(void)
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);


	cin >> t;

	random_device rd;
	mt19937_64 gen(rd());


	while (t--)
	{
		cin >> n >> m >> d >> k;

		init();

		int ans = 0;

		for (int i = 1; i <= m; i++)
		{
			int u, v;

			cin >> u >> v;

			merge(0, u, v);
		}

		map<ull, int>mph1;

		for (int i = 1; i <= d + 1; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				hs[i][j] = gen();

				sons[i][j] = sons[0][j];

				sz[i][j] = sz[0][j];

				p[i][j] = p[0][j];
			}
		}

		for (int i = 1; i <= d + 1; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				int fj = find(i,j);

				h[j] += hs[i][fj];
			}
		}


		for (int i = 1; i <= n; i++)
		{
			mph1[h[i]]++;
		}

		for (auto it : mph1)
		{
			ans += it.second*(it.second - 1)/2;
		}

		//cout << ans << '\n';
	
		
		while (k--)
		{
			被合并的点的(父亲的哈希值的和)的数量
			//map<ull, int>mph2;

			int u, v, w;

			cin >> u >> v >> w;

			
			int fu = find(w, u);
			int fv = find(w, v);

			if (fu == fv)
			{
				cout << ans << '\n';
				continue;
			}

			if (sz[fu] < sz[fv])
			{
				swap(u, v);
				swap(fu, fv);
			}


			set<int>st;


			for (auto it : sons[w][fv])
			{
				int tmp = h[it];

				if (!st.count(tmp))
				{
					st.insert(tmp);

					ans -= mph1[tmp] * (mph1[tmp] - 1) / 2;
				}

				tmp = h[it] + hs[w][fu] - hs[w][fv];

				if (!st.count(tmp))
				{
					st.insert(tmp);

					ans -= mph1[tmp] * (mph1[tmp] - 1) / 2;
				}

			}	

			for (auto it : sons[w][fv])
			{
				mph1[h[it]]--;

				if (mph1[h[it]] == 0)
				{
					mph1.erase(h[it]);
				}


				h[it] += hs[w][fu] - hs[w][fv];

				sons[w][fu].push_back(it);

				mph1[h[it]]++;
			}


			sz[w][fu] += sz[w][fv];
			p[w][fv] = fu;


			sz[w][fv] = 0;
			sons[w][fv].clear();
			sons[w][fv].shrink_to_fit();


			for (auto it : st)
			{
				ans += mph1[it] * (mph1[it] - 1) / 2;
			}


			cout << ans << '\n';
		}

		for (int i = 0; i <= d + 1; i++) {
			for (int j = 0; j <= n; j++) {
				hs[i][j] = 0;
				p[i][j] = sz[i][j] = 0;
				hs[i][j] = 0;
				sons[i][j].clear();
				sons[i][j].shrink_to_fit();
			}
		}
		for (int i = 0; i <= n; i++) {
			h[i] = 0;
		}



	}


	system("pause");
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值