hihocoder第237周:三等分帶權樹
阿新 • • 發佈:2019-01-20
問題分析 ret pen using 結點 pro problems ota cpp
題目鏈接
問題描述
給定一棵樹,樹中每個結點權值為[-100,100]之間的整數。樹中包含結點總數不超過1e5。任選兩個非根節點A、B,將這兩個結點與其父節點斷開,可以得到三棵子樹。現要求三棵子樹的權值之和相等,問A、B有多少種選擇方法。
輸入
T:樣例種數
N:樹中結點個數
v1 father1
v2 father2
問題分析
此問題是一道樹形DP。
如何才能將一棵樹劃分成三棵權值之和相等的子樹?有兩種情況:
- 若結點x和結點y沒有血緣關系(不是祖孫關系),則x和y的權值之和都是s(s為整棵樹的權值之和的三分之一),x和y把這棵樹截為三段。
- 若結點x和結點y有血緣關系,不妨設x是y的祖先,則x的權值之和為2s,y的權值之和為s。x和y把這棵樹截為三段。
最精簡的代碼
#include<iostream> #include<stdio.h> #include<iostream> using namespace std; const int maxn = 1e5 + 7; typedef long long ll; struct Node { int v; int s; int son; }a[maxn]; int nex[maxn]; int root; int per; int n; ll ans; int perCount = 0; int go(int nodeId) { int temp = perCount; a[nodeId].s = a[nodeId].v; for (int i = a[nodeId].son; i != -1; i = nex[i]) { a[nodeId].s += go(i); } if (nodeId != root) { if (a[nodeId].s == per * 2) { ans += perCount - temp; } if (a[nodeId].s == per) { ans += temp; perCount++; } } return a[nodeId].s; } void push(int parent, int son) { int temp = a[parent].son; nex[son] = temp; a[parent].son = son; } int main() { int T; cin >> T; while (T-- > 0) { cin >> n; for (int i = 0; i <= n; i++) { a[i].son = -1; nex[i] = -1; } int s = 0; for (int i = 0; i < n; i++) { int father; cin >> a[i].v >> father; s += a[i].v; father--; if (father == -1) { root = i; } else { push(father, i); } } if (s % 3 == 0) { per = s / 3; ans = 0; perCount = 0; go(root); cout << ans << endl; } else { cout << 0 << endl; } } return 0; }
復雜但是直觀的代碼
#include<iostream> #include<stdio.h> #include<stdlib.h> using namespace std; const int maxn = 1e5 + 7; typedef long long ll; int n; int per;//每個分支應該等於的數值 struct Node { int v;//結點權重 int s;//結點所代表的子樹的權重之和 int sonPerCount;//子樹中權值之和為per的結點個數(包括自身) int son;//兒子結點,鏈表第一個結點 int next;//下一個兄弟結點 }a[maxn]; void push(int father, int son) { int temp = a[father].son; a[son].next = temp; a[father].son = son; } int root; void init(int nodeId) { a[nodeId].s = a[nodeId].v; for (int i = a[nodeId].son; ~i; i = a[i].next) { init(i); a[nodeId].s += a[i].s; a[nodeId].sonPerCount += a[i].sonPerCount; } if (a[nodeId].s == per) { a[nodeId].sonPerCount++; } } ll count1 = 0, count2 = 0;//count1表示我的值為per時,count2表示我的值為2per時 int totalPer = 0; int cnt = 0; ll ans = 0; void go(int nodeId) { if (a[nodeId].s == per) { if (nodeId != root) { count1 += totalPer - cnt - a[nodeId].sonPerCount; } cnt++; } //此處不能有else,因為當per=0時,子樹中的總和為per的結點也會生效 if (a[nodeId].s == per * 2) { if (nodeId != root) { count2 += a[nodeId].sonPerCount; if (per == 0)count2--;//因為sonPerCount包括結點自身,所以需要先去掉結點 } } for (int i = a[nodeId].son; ~i; i = a[i].next) { go(i); } if (a[nodeId].s == per)cnt--; } int main() { freopen("in.txt", "r", stdin); int T; cin >> T; while (T--) { cin >> n; int s = 0; for (int i = 1; i <= n; i++) { a[i].sonPerCount = 0; a[i].son = -1; a[i].next = -1; } for (int i = 1; i <= n; i++) { int father; cin >> a[i].v >> father; if (father == 0) { root = i; } else { push(father, i); } s += a[i].v; } if (s % 3 == 0) { per = s / 3; init(root); count1 = count2 = 0; totalPer = 0; for (int i = 1; i <= n; i++) { if (a[i].s == per)totalPer++; } go(root); ans = count1 / 2 + count2; cout << ans << endl; } else { cout << 0 << endl; } } return 0; }
註意事項
- 如果用Java寫,此題會超時。因為輸入量比較大
- 如果數據充分一些,1e5的復雜度有可能爆棧,所以需要使用棧的方式來遍歷樹(也可以先對樹進行線索化)。
hihocoder第237周:三等分帶權樹