题意
给定一张图以及q个查询,输出每个查询中的边是否全部会出现在某个最小生成树里。
思路
首先如果只考虑一次查询,这一次查询只有一条边,那么只要用kruskal算法处理完所有边权小于这条边的边,此时如果这条边的两点已经在同一个连通块里了,那么这条边不可能在MST里,反之则一定可以在某一个MST里。
对于多次查询,每次查询多条边来说类似,先存下所有的询问,然后按照边从小到大开始遍历,如果一组询问中有一条边不符合,那么这个询问就是NO的。
用可撤销并查集来维护每次询问每种边权增加后还原的操作。
代码
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N = 5e5 + 100;
const int mod = 1000000007;
const int INF = 0x3f3f3f3f;
int n, m;
struct node {
int u, v, w;
} a[N];
int fa[N], sz[N];
vector<pair<int &, int>> his_sz, his_fa;
map<int, vector<int>> mp[N];
vector<int> ve[N];
int ans[N];
int find(int x) {
while (x != fa[x])
x = fa[x];
return fa[x];
}
void union_set(int x, int y) {
x = find(x);
y = find(y);
if (x != y) {
if (sz[x] < sz[y])
swap(x, y);
his_sz.emplace_back(sz[x], sz[x]);
sz[x] += sz[y];
his_fa.emplace_back(fa[y], fa[y]);
fa[y] = x;
}
}
int history() {
return his_fa.size();
}
void roll(int h) {
while (his_fa.size() > h) {
his_fa.back().first = his_fa.back().second;
his_fa.pop_back();
his_sz.back().first = his_sz.back().second;
his_sz.pop_back();
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
a[i].u=u;
a[i].v=v;
a[i].w=w;
ve[w].push_back(i);
}
for(int i=1;i<=n;i++){
fa[i]=i;
sz[i]=1;
}
int q;
cin >> q;
for (int j = 1; j <= q; j++) {
int k;
cin >> k;
for (int i = 1; i <= k; i++) {
int idx;
cin >> idx;
mp[a[idx].w][j].push_back(idx);
}
}
for (int w = 1; w <= 500010; w++) {
if (ve[w].empty())continue;
for (auto x: mp[w]) {
if (ans[x.first] == 1)continue;
int h = history();
for (int y: x.second) {
if (find(a[y].u) == find(a[y].v))ans[x.first] = 1;
union_set(a[y].u, a[y].v);
}
roll(h);
}
for (auto x: ve[w]) {
union_set(a[x].u, a[x].v);
}
}
for (int i = 1; i <= q; i++) {
if(ans[i]==1)cout<<"NO"<<'\n';
else cout<<"YES"<<'\n';
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin>>t;
while (t--) solve();
return 0;
}
题意
给定一课带边权的树,对于一条从u到v的简单路径, f(u,v) 表示路径上只出现一次的边权的数量,输出所有 f(u,v) 的和。
思路
对于所有边权为 w 的边,它们对于答案的贡献就是只经过一次边权为 w 的路径的数量。因此可以把树中所有边权为 w 的边删除,剩余了一些连通块,这些连通块内没有边权为 w 的边,连通块之间原本是有一条边权为 w 的边,这样就比较容易计算了, w 对于答案的贡献就是所有连通块的大小两两相乘的和。
实现用简单的分治完成
代码
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N = 5e5 + 100;
const int mod = 1000000007;
const int INF = 0x3f3f3f3f;
vector<pair<int,int>>to[N];
int fa[N], sz[N];
vector<pair<int &, int>> his_sz, his_fa;
int find(int x) {
while (x != fa[x])
x = fa[x];
return fa[x];
}
void union_set(int x, int y) {
x = find(x);
y = find(y);
if (x != y) {
if (sz[x] < sz[y])
swap(x, y);
his_sz.emplace_back(sz[x], sz[x]);
sz[x] += sz[y];
his_fa.emplace_back(fa[y], fa[y]);
fa[y] = x;
}
}
int history() {
return his_fa.size();
}
void roll(int h) {
while (his_fa.size() > h) {
his_fa.back().first = his_fa.back().second;
his_fa.pop_back();
his_sz.back().first = his_sz.back().second;
his_sz.pop_back();
}
}
int dfs(int l,int r){
if(l==r){
int res=0;
for(auto p:to[l]){
res+=sz[find(p.first)]*sz[find(p.second)];
}
return res;
}
int res=0;
int mid=(l+r)/2;
int h=history();
for(int i=l;i<=mid;i++){
for(auto p:to[i]){
union_set(p.first,p.second);
}
}
res+=dfs(mid+1,r);
roll(h);
h=history();
for(int i=mid+1;i<=r;i++){
for(auto p:to[i]){
union_set(p.first,p.second);
}
}
res+=dfs(l,mid);
roll(h);
return res;
}
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
fa[i]=i;
sz[i]=1;
if(i==n)continue;
int u,v,w;
cin>>u>>v>>w;
to[w].emplace_back(u,v);
}
cout<<dfs(1,n);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin>>t;
while (t--) solve();
return 0;
}