链接:https://cf17-tournament-round3-open.contest.atcoder.jp/tasks/asaporo2_e
题目大意:给定一颗有n个节点的树,每条边有一个权重c, 对于i,X表示对于所有j!=i,从i到j的路径上最小的c之和,求对于所有的i,X分别等于多少?
分析:将所有边按权重排序从大到小,每次插入一条边,对于这条边两边的两个连通块A、B来说,路径上的最小权重都是c,所以对A中的点全部加c*|B|,对B也一样,即可得到答案。这样复杂度是O(n^2)的,合并点集用的是并查集,加一个add数组,使得i的答案是从i到祖先的路径上所有add之和,然后每次更新都是O(1)的,总复杂度为O(n)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int maxn = 1e5 + 5; 7 typedef long long ll; 8 int p[maxn], n; 9 ll add[maxn], cnt[maxn]; 10 struct Edge{ 11 int a, b; 12 ll c; 13 }e[maxn]; 14 bool Cmp(Edge a, Edge b){return a.c > b.c;} 15 int Find(int x){ 16 if(p[x] == x)return x; 17 if(Find(p[x]) == p[x])return p[x]; 18 add[x] += add[p[x]]; 19 p[x] = p[p[x]]; 20 //add[x] -= add[p[x]]; 21 return p[x]; 22 } 23 void Merge(int a, int b, ll c){ 24 int x = Find(a), y = Find(b); 25 add[x] += c * cnt[y]; 26 add[y] += c * cnt[x]; 27 cnt[x] += cnt[y]; 28 add[y] -= add[x]; 29 p[y] = x; 30 } 31 int main(){ 32 // freopen("e:\in.txt","r",stdin); 33 cin>>n; 34 int a, b; 35 ll c; 36 for(int i = 0; i < n - 1; i++)scanf("%d%d%lld", &e[i].a, &e[i].b, &e[i].c); 37 sort(e, e + n - 1, Cmp); 38 for(int i = 1; i <= n; i++)p[i] = i, add[i] = 0, cnt[i] = 1; 39 for(int i = 0; i < n - 1; i++){ 40 Merge(e[i].a, e[i].b, e[i].c); 41 } 42 // for(int i = 1; i <= n; i++){ 43 // cout<<p[i]<<' '<<add[i]<<endl; 44 // } 45 for(int i = 1; i <= n; i++){ 46 Find(i); 47 // cout<<p[i]<<' '<<add[i]<<endl; 48 ll ans; 49 if(p[i] == i)ans = add[i]; 50 else ans = add[i] + add[p[i]]; 51 printf("%lld ", ans); 52 } 53 return 0; 54 }