*题解:P10180 半彩三重奏

题目链接

解析

对于一棵有 \(n\) 个结点的树,想想怎么求其内部简单路径个数。其实很简单,对于路径的两个端点,每个端点都有 \(n\) 种选择,根据乘法原理路径个数一共就是 \(n^2\) 种。

那么现在我们可以处理一种颜色的答案了。只需将相连的相同颜色的点视为一个连通块,统计所有连通块大小的平方和即可。这个过程可以用并查集来实现。

对于题目要求的两种颜色 \(x,y\),首先可以想到找到连接 \(x,y\) 两种颜色的所有边,再将对应连通块合并起来,统计完答案之后在撤销回去。树上有 \(n - 1\) 条边,分别对应着 \(n - 1\) 次合并,而每条边只在询问颜色恰好为该边连接的两颜色时将两连通块合并。所以预处理所有需要合并的询问,总共只需处理 \(n - 1\) 次合并。每次合并 \(a,b\) 会对答案做 \(2\cdot siz_a \cdot siz_b\) 的贡献。

此题卡 \(\log\) 的常,使用 unordered_map,期望时间复杂度 \(O(n + q)\)

可撤销并查集

撤销时要还原的是 \(fa\) 和按秩合并要用到的 \(siz\)。合并时,设 \(siz\) 小的并查集的根为 \(x\),另一个为 \(y\)。那么应当是 \(fa_x \leftarrow y\)\(siz_y \leftarrow siz_y + siz_x\)。记录原始的 \(fa_x,siz_y\),撤销时按照合并顺序的倒序进行还原即可。注意不能用路径压缩。

代码

/*
*/
#include <bits/stdc++.h>
#define eps 0.0000000001
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define getchar getchar_unlocked
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e6 + 5,M = 5e5 + 5,P = 2000000,mod = 998244353;
int u[N],v[N];
unordered_map<int,ll> res[N];
unordered_map<int,vector<pii> > e[N];//e[i][j] 存储连接颜色 i 和 j 的所有边
vector<int> vt[N];
int fa[N],siz[N],a[N];
bool vis[N]; 
int find(int x){
	if(fa[x] != x) return find(fa[x]);
	return fa[x];
}
struct S{
	int a,b,faa,sizb;
};
stack<S> s;
void merge(int a,int b,bool f){
	a = find(a),b = find(b);
	if(a == b) return;
	if(siz[a] > siz[b]) swap(a,b);
	if(f){
		s.push({a,b,fa[a],siz[b]});
	}
	fa[a] = b;
	siz[b] += siz[a];
}
void undo(){
	while(!s.empty()){
		S x = s.top();
		s.pop();
		fa[x.a] = x.faa;
		siz[x.b] = x.sizb;
	}
}
int read(){
	int a = 1,x = 0;
	char ch = getchar();
	while(ch > '9' || ch < '0'){
		if(ch == '-') a = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9'){
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return a * x;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n = read(),q = read();
	for(int i=1;i<=n;i++){
		a[i] = read();
		vt[a[i]].push_back(i);
		fa[i] = i;
		siz[i] = 1;
	}
	for(int i=2;i<=n;i++){
		u[i] = i;
	 	v[i] = read();
		if(a[u[i]] > a[v[i]]) swap(u[i],v[i]);
		if(a[u[i]] == a[v[i]]){
			merge(u[i],v[i],0);
		}
		e[a[u[i]]][a[v[i]]].push_back({u[i],v[i]});
	}
	for(int i=1;i<=n;i++){
		ll x = 0;
		for(int j : vt[i]){
			int f = find(j);
			if(!vis[f]){
				x += 1ll * siz[f] * siz[f];
				vis[f] = true;
			}
		}
		res[i][i] = x;
		for(pair<int,vector<pii> > v : e[i])if(v.first != i){
			x = 0;
			for(pii p : v.second){
				int f1 = find(p.first),f2 = find(p.second);
				if(f1 != f2)
					x += 2ll * siz[f1] * siz[f2];					
				merge(p.first,p.second,1);
				int f = find(p.first);
						
			}
			res[i][v.first] = x;
			undo();
		}
		
	}
	while(q--){
		int x = read(),y = read();
		if(x > y) swap(x,y);
		if(!res[x].count(y)){
			cout<<res[x][x] + res[y][y]<<'\n';
		}else{
			cout<<res[x][y] + res[x][x] + res[y][y]<<'\n';//横跨两个颜色的的加上单个颜色的
		}
	}
	return 0;
}

posted @ 2026-06-09 21:51  yutar  阅读(14)  评论(0)    收藏  举报