[USACO18JAN] Cow at Large P

发布时间 2023-10-29 10:36:27作者: Trmp

题目链接

赛时只打了暴力。

Part1

我们考虑在什么情况下要放一个村民,我们设根节点的深度为 \(0\), 那么对于一个节点 \(u\) ,如果在其子树内有一个叶子结点 \(v\), 满足 \(dis_{u, v} \leqslant dep_u\), 那么只要在这个节点放一个村民,就可以把 \(u\) 节点堵住,原因显然。

我们想要让村民尽量少,那么就要让被堵住的节点尽量少,尽量靠上,这样一个村民的价值才能发挥到最大。设一个节点 \(u\) 到最近叶子的距离为 \(mnd_u\), 那我们要找的就是几棵最大的子树(子树根节点为 \(u\) ,其父节点为 \(fa\) ),满足 \(mnd_u \leqslant dep_u\) ,这个节点显然满足 \(mnd_u \leqslant dep_u\)\(mnd_{fa} > dep_{fa}\) , 答案就是这种节点的个数。

Part2

题目要求求出每个节点作为出发点时的答案,直接统计不好算,我们考虑转化。

对于一棵子树,设其节点个数为 \(size_u\), 设一个节点 \(i\) 的度数为 \(deg_i\), 那么必然有:

\[\sum_{i\in sutree_u}deg_i=2\times size_u-1 \implies \sum_{i\in sutree_u}(2-deg_i)=1 \]

那我们设一个节点的权值 \(val_i\)\((2-deg_i)\), 那么对于任意一棵子树,子树内节点的权值和为 \(1\). 那么统计所有满足条件的点,其权值和即为答案,一个节点 \(v\) 对节点 \(u\) 有贡献, 当且仅当 \(dis_{u,v} \geqslant mnd_v\)。证明可以考虑,放村民的那个叶子结点 \(v\), 和这棵极大子树内的任意一个节点 \(u\) 的距离 一定不大于 \(u\) 和根节点的距离。这个可以画图理解一下。

Part3

现在把问题转化成了树上点对贡献问题, 可以使用点分治和树状数组解决。

具体的,设当前的分治中心为 \(root\) ,那么那么可以把条件改变一下,拆分成:

\[dis_{u,root}+dis_{v,root} \geqslant mnd_v \implies dis_{u,root} \geqslant mnd_v-dis_{v,root} \]

每次遍历整棵分治树,把节点 \(v\) 的权值放到下标为 \(mnd_v-dis_{v,root}\) 的位置,处理答案时,先删去一棵子树的信息,子树内的节点 \(u\) 查询 \(dis_{u,root}\), 累加贡献。然后再把这棵子树的信息加上。重复这个过程。处理完整棵分治树后集体删去信息,再递归分治树就可以了(不要忘了计算分治中心的贡献)。

复杂度为 $\Theta(n\log^2 n) $。 可以通过。

Code
#include <iostream>
#include <cstdio>
#include <queue>

const int N=7e4+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, cnt_edge;
struct edge {
    int next, to;
}e[N<<1];
int head[N], du[N];
int mnd[N], val[N], ans[N];

void add_edge(int u,int v) {
    e[++cnt_edge].to=v;
    e[cnt_edge].next=head[u];
    head[u]=cnt_edge;
    du[v]++;
}

queue <int> q;

void BFS() {
    for(int i=1;i<=n;i++) mnd[i]=n+1;
    for(int i=1;i<=n;i++) {
        if(du[i]==1) {
            mnd[i]=0;
            q.push(i);
        }
    }

    while(!q.empty()) {
        int now=q.front(); q.pop();
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(mnd[v] > mnd[now]+1) {
                mnd[v]=mnd[now]+1;
                q.push(v);
            }
        }
    }
}

struct BIT {
    int t[N<<1];

    inline int lowbit(int x) {return x&(-x);}

    inline void up_date(int pos,int val) {
        while(pos<=n*2) {
            t[pos]+=val;
            pos+=lowbit(pos);
        }
    }

    inline int query(int pos) {
        int res=0;
        while(pos) {
            res+=t[pos];
            pos-=lowbit(pos);
        }
        return res;
    }
}B;

struct Point_Part {
    int sum_root, root;
    int maxp[N], dis[N], size[N];
    bool vis[N];
    
    void get_root(int now,int fa) {
        size[now]=1, maxp[now]=0;
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa || vis[v]) continue;
            get_root(v, now);
            size[now]+=size[v];
            maxp[now]=max(maxp[now], size[v]);
        }
        maxp[now]=max(maxp[now], sum_root-maxp[now]);
        if(maxp[now]<maxp[root]) root=now;
    }

    void get_dis(int now,int fa) {
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa || vis[v]) continue;
            dis[v]=dis[now]+1;
            get_dis(v, now);
        }
    }

    void change(int now,int fa,int type) {
        int pos=mnd[now]-dis[now]+n;
        B.up_date(pos, type*val[now]);
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa || vis[v]) continue;
            change(v ,now ,type);
        }
    }

    void calc(int now,int fa) {
        int pos=dis[now]+n;
        ans[now]+=B.query(pos);
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa || vis[v]) continue;
            calc(v ,now);
        }
    }

    void solve(int now) {
        vis[now]=1, dis[now]=0;
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            dis[v]=dis[now]+1;
            get_dis(v, now);
        }

        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            change(v, now, 1);
        }

        ans[now]+=B.query(n);
        int pos=mnd[now]+n;
        B.up_date(pos, val[now]);

        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            change(v ,now, -1);
            calc(v, now);
            change(v, now ,1);
        }

        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            change(v, now, -1);
        }
        B.up_date(pos, -val[now]);

        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;

            sum_root=size[v], root=0;
            get_root(v ,0);
            solve(root); 
        }
    }

    void work() {
        maxp[0]=n+1, sum_root=n;
        get_root(1, 0);
        solve(root);
    }
}P;

int main() {

    n=read();
    for(int i=1;i<n;i++) {
        int u=read(), v=read();
        add_edge(u, v);
        add_edge(v, u);
    }

    BFS();

    for(int i=1;i<=n;i++) {
        val[i]=2-du[i];
    }

    P.work();

    for(int i=1;i<=n;i++) write(ans[i]);

    return 0;
}