hihocoder [Offer收割]编程练习赛52 D 部门聚会

看了题目的讨论才会做的

首先一点,算每条边(u, v)对于n*(n+1)/2种[l, r]组合的贡献
正着算不如反着算
哪些[l, r]的组合没有包含这条边(u, v)呢
这个很好算
只需要统计u这半边的点中有哪些连续数字,连续的数字就是一个[l, r]组合
就可以算出u这半边有哪些潜在的[l, r]组合
当然u这半边算好了,v这半边正好是u的数字反过来
这个过程可以使用set来统计,很好写

现在我们解决了对于一个边怎么算贡献
现在需要使用点分治

使用点分治求重心进行遍历保证了每个点至多被放入set log(n)次
所以复杂度大约nlog(n)

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
#define MS(x, y) memset(x, y, sizeof(x))
#define MP(x, y) make_pair(x, y)
const int INF = 0x3f3f3f3f;

int n;
struct GraphUnit {
    int to, next;
} Tree[N << 1];
int head[N], tot;
bool vis[N];
void addEdge(int from, int to) {
    Tree[tot].to = to;
    Tree[tot].next = head[from];
    head[from] = tot++;
}
set<int> vertexSet;
int min_MaxSon_Centrol_Num;
int min_MaxSon_Centrol_Pos;
ll ans = 0;
int getCentrol(int vertex, int preVertex) {
    int fatherSum = 1;
    int maxSon = -1;
    vertexSet.insert(vertex);
    for (int i = head[vertex]; ~i; i = Tree[i].next) {
        int to = Tree[i].to;
        if (to == preVertex || vis[to])
            continue;
        int sonSum = getCentrol(to, vertex);
        maxSon = max(maxSon, sonSum);
        fatherSum += sonSum;
    }
    maxSon = max(maxSon, n - fatherSum);
    if (maxSon < min_MaxSon_Centrol_Num) {
        min_MaxSon_Centrol_Num = maxSon;
        min_MaxSon_Centrol_Pos = vertex;
    }
    return fatherSum;
}
ll Cal(int x) {
    return 1ll * x * (x + 1) / 2;
}
void solveTheSet() {
    int pre = 0;
    int cnt = 0;
    ll preans = ans;
    for (auto it = vertexSet.begin(); it != vertexSet.end(); ++it) {
        int number = *it;
        if (number == pre + 1)
            cnt++;
        else {
            ans += Cal(cnt);
            ans += Cal(number - pre - 1);
            cnt = 1;
        }
        pre = number;
    }
    ans += Cal(cnt);
    ans += Cal(n - pre);
    //   printf("hh :%lld
", ans - preans);
}
void divideAndConquer(int vertex, int preVertex) {
    min_MaxSon_Centrol_Num = INF;
    vertexSet.clear();
    getCentrol(vertex, vertex);
    int root = min_MaxSon_Centrol_Pos;
    // printf("%d
", root);
    vis[root] = true;
    if (vertex != preVertex) {  // only the first we don't need to solve, because it don't have the pre edge
        solveTheSet();
    }
    for (int i = head[root]; ~i; i = Tree[i].next) {
        int to = Tree[i].to;
        if (vis[to])
            continue;
        divideAndConquer(to, root);
    }
}
int main() {
    while (~scanf("%d", &n)) {
        memset(vis, 0, sizeof(vis));
        memset(head, -1, sizeof(head));
        tot = 0;
        ans = 0;
        for (int i = 1; i < n; ++i) {
            int from, to;
            scanf("%d %d", &from, &to);
            addEdge(from, to);
            addEdge(to, from);
        }

        divideAndConquer(1, 1);

        printf("%lld
", Cal(n) * (n - 1) - ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Basasuya/p/8668317.html