hdu6881(求删除尽可能小的点使得树的直径不超过K,统计距离点长度大于k/2的点数,距离边长度大于k/2+1的点数)

题:http://acm.hdu.edu.cn/showproblem.php?pid=6881

题意:给定n个节点的树,问删除尽可能小的点使得树的直径不超过K,输出最小删除的点数,(1<=k<=n<=3e5)

分析:

   核心:枚举中间点或中间边;

   枚举中间点的情况是k是偶数,因为直径长可被“劈”成均等的俩半,枚举中间边的情况是k为奇数;

   考虑树分治,每次找重心去考虑;

   k是偶数的情况:点u作为直径中心点,那么距离u长度大于k/2的点数均要删去;

            那么就每次取重心,重心和孩子分别来考虑;

            统计不在子树u的点子树u的每一个节点造成的贡献;

            通过先求总的深度计数cnt[],和当前子树u的深度计数cnt2[];

            做差就能求出其他子树对自己的影响而不会统计到自己对自己;

   k为奇数的情况:边作为直径中心边,距离当前边长度大于k/2+1的点数均要删去;

            边和点不同,所以当枚举的边是当前重心root时,还要加上自己子树长度大于k/2+1的点数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int inf=0x3f3f3f3f;
const int M=3e5+5;
struct node{
    int v,nextt;
}e[M<<1];
int T,n,K,tot,sum,root;
int head[M],f[M],vis[M],sz[M],cnt[M],cnt1[M],res[M],num1,num2;
void init(){
    tot=1;
    for(int i=1;i<=n;i++)
        vis[i]=head[i]=f[i]=res[i]=cnt[i]=sz[i]=0;
}
void addedge(int u,int v){
    tot++;
    e[tot].v=v;
    e[tot].nextt=head[u];
    head[u]=tot;
}
void find_root(int u,int fa){///找重心
    sz[u]=1;
    f[u]=0;
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(v!=fa&&!vis[v]){
            find_root(v,u);
            f[u]=max(f[u],sz[v]);
            sz[u]+=sz[v];
        }
    }
    f[u]=max(f[u],sum-sz[u]);
    if(f[u]<f[root]) root = u;
}

void dfs1(int u,int fa,int dep){///计算cnt1[]
    cnt[dep]++;
    num1=max(num1,dep);
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(!vis[v]&&v!=fa) dfs1(v,u,dep+1);
    }
}

void dfs2(int u,int fa,int dep){
    cnt1[dep]++;
    num2=max(num2,dep);
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(v!=fa&&!vis[v]) dfs2(v,u,dep+1);
    }
}

void dfs3(int u,int fa,int dep,int curedge){
    if(K&1){
        int D=max(0,(K/2+1)-(dep-1));///距离这条边长度大于等于(K/2+1)的个数,就是是长度大于K/2的个数
        res[curedge]+=cnt[D]-cnt1[D];
        if(fa==root)///当前边是和root连的边时,要算子树u中对当前边的贡献,
            res[curedge]+=cnt1[(K/2+1)+1];///之所以后者要+1,时是因为要从当前边开始往下找,而当前边深度对于root来说一定是1;
    }
    else{
        int D=max(0,K/2+1-dep);
        res[u]+=cnt[D]-cnt1[D];
    }
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(!vis[v]&&v!=fa)
            dfs3(v,u,dep+1,i/2);
    }
}
void solve(int u){
    vis[u]=1;
    num1=0;
    cnt[0]=1;///总的
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(!vis[v]) dfs1(v,u,1);
    }
    for(int i=num1-1;i>=0;i--) cnt[i]+=cnt[i+1];
    if(K%2==0) res[u]+=cnt[K/2+1];///当前重心与它的孩子分开算

    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(!vis[v]){
            num2=0;
            ///深度为0的点固然存在,所以不用做cnt1[0]=1的操作
            dfs2(v,u,1);
            for(int j=num2-1;j>=0;j--) cnt1[j]+=cnt1[j+1];
            dfs3(v,u,1,i/2);/// i/2为边的编号
            for(int j=num2;j>=0;j--) cnt1[j]=0;
        }
    }
    for(int i=num1;i>=0;i--) cnt[i]=0;
    for(int i=head[u];i;i=e[i].nextt){
        int v=e[i].v;
        if(!vis[v]){
            sum=sz[v];
            root=0;
            find_root(v,u);
            solve(root);
        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&K);
        init();
        for(int u,v,i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        f[0]=inf;
        root=0;
        sum=n;
        find_root(1,0);
        solve(root);
        int ans=inf;
        for(int i=1;i<n;i++)
            ans=min(ans,res[i]);
        if(K%2==0)///偶数看点,奇数看边,而边只有n-1条
            ans=min(ans,res[n]);
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

posted @ 2020-10-08 16:26  starve_to_death  阅读(326)  评论(0编辑  收藏  举报