一道树题
题目大意:
给定一棵树,边的编号为读入顺序。现在规定,区间$[L, R]$的贡献$S(L,R)$为把编号在该区间里的边都连上后,当前形成的森林中点数大于等于$2$的联通块个数。
求$sumlimits_{i = 1} ^ {N - 1}sumlimits_{j = i} ^ {N - 1}S(i,j)$。
数据范围:$2le Nle 10^5$。
题解:
水题。
我们发现,一棵树上假设联通了$k$条边,那么联通块个数就是$N-k$个。所以我们可以求出,所有区间下的所有联通块个数和。
现在我们要减掉,每个区间中形成的点联通块。
这个好办。
假设以这个点$p$为端点的边的编号从小到大一次为$a_1$一直到$a_m$。
那么如果一个区间满足这个区间不跨过任意一个$a$即可,这个就是两个$a$之间求一个区间个数。
由于我们需要把每个点的$a$数组排序,所以复杂度是$O(nlogn)$。
代码:
#include <bits/stdc++.h> #define N 200010 using namespace std; typedef long long ll; int head[N], to[N << 1], nxt[N << 1], val[N << 1], tot, n; ll ans; char *p1, *p2, buf[100000]; #define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ ) int rd() { int x = 0, f = 1; char c = nc(); while (c < 48) { if (c == '-') f = -1; c = nc(); } while (c > 47) { x = (((x << 2) + x) << 1) + (c ^ 48), c = nc(); } return x * f; } inline void add(int x, int y, int z) { to[ ++ tot] = y; val[tot] = z; nxt[tot] = head[x]; head[x] = tot; } int a[N]; inline ll calc(int l, int r) { // printf("%d %d ", l, r); if (l > r) { return 0; } ll len = r - l + 1; return len * (len + 1) / 2; } void dfs(int p, int fa) { // cout << p << endl ; int cnt = 0; for (int i = head[p]; i; i = nxt[i]) { a[ ++ cnt] = val[i]; } sort(a + 1, a + cnt + 1); a[0] = 0; for (int i = 1; i <= cnt; i ++ ) { ans -= calc(a[i - 1] + 1, a[i] - 1); } ans -= calc(a[cnt] + 1, n - 1); for (int i = head[p]; i; i = nxt[i]) { if (to[i] != fa) { dfs(to[i], p); } } } int main() { n = rd(); for (int i = 1; i < n; i ++ ) { int x = rd(), y = rd(); add(x, y, i); add(y, x, i); } for (int i = 1; i < n; i ++ ) { ans += (ll)(n - i) * (n - i); } // cout << ans << endl ; dfs(1, 1); cout << ans << endl ; return 0; }
小结:真难则反真的是好用,而且我们要知道哪些我们能处理,哪些不能处理。