小 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]==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;
}