题: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 ",ans); } return 0; }