891C Envy
题意
给定一张图以及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;
}
Unique Occurrences
题意
给定一课带边权的树,对于一条从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;
}