点分治问题 ----------- HDU6881 Tree Cutting or 2020杭电多校第10场 [点分治+思维]

题目链接


题目大意:

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


解题思路:

这道题和AGC001的c题是一模一样的,但是这道题数据量比较大,但是核心思想还是不变的还是,枚举每个点作为直径的中间点,或者枚举边作为中心边,但是AGC001的c题数据量比较小可以暴力枚举这个题有 3 e 5 3e5 3e5,我们应该考虑如何优化暴力。

对于树上统计问题我们最先想到的就是点分治。

1.对于 K K K是偶数的情况:因为 K K K是偶数所以直径上的点就只有奇数个,那么对于每一个点 u u u我们假设这个点是直径的中点,那么距离 u u u长度大于 k / 2 k/2 k/2的都要删掉,
那么每次就取重心,重心和孩子分别考虑;
     1.1对于每个点 u u u我们考虑和点 u u u不在同一颗子树里面的点对这个点的影响,在同一个子树里面的点通过分治会递归下去求解。
     1.2首先我们先整个分治的树求一共深度计数 n u m [ ] ( 就 是 统 计 一 下 各 个 深 度 的 点 有 多 少 个 ) num[](就是统计一下各个深度的点有多少个) num[]()
     1.3对于每个子树求解答案的时候,我们先对这个子树求出属于它的 n u m 1 [ ] num1[] num1[] n u m num num n u m 1 num1 num1做差就是其他子树的影响了。
     1.4为了快速减我们可以求个后缀和。因为是大于 K / 2 K/2 K/2都要减掉

2.对于 K K K是奇数的情况:因为 K K K奇数那么中心位置是一个边,那么我们的答案就是枚举边做直径的中点,对于距离当前边的距离大于等于 k / 2 + 1 k/2+1 k/2+1的点均要去掉。
     2.1:注意一点就是我们点分治的时候是枚举点,但是统计答案的时候是边。你从父节点到下一个分治重心的时候,过渡的边 ( r o o t − > s o n ) (root->son) (root>son)会只统计其他子树对这条边的影响,所以我们要为这些边直接加上自己子树上大于等于 K / 2 + 1 K/2+1 K/2+1的点的个数。


#pragma comment(linker,"/STACK:102400000,102400000")
#pragma GCC optimize(2)
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 3e5 + 10;
struct node {
   int nxt, to;
}edge[maxn<<1];
int head[maxn], cnt;
int n, k;
inline void add(int from, int to) {
	edge[++cnt] = {head[from],to};
	head[from] = cnt; 
}
//..........
bool vis[maxn];
int MX, root, max_son[maxn], now_num ,siz[maxn];
void getroot(int u, int fa) {
    max_son[u] = 0;
	siz[u] = 1; 
    for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if(v == fa || vis[v]) continue;
		getroot(v,u);
		siz[u] += siz[v];
		max_son[u] = max(max_son[u],siz[v]);
	}
    max_son[u] = max(max_son[u],now_num - siz[u]);
	if(max_son[u] < MX) MX = max_son[u], root = u; 
}
//..................................
int num[maxn], max_depth;
int res[maxn];
void dfs1(int u, int  fa, int depth) {//对整个树dfs
   	num[depth] ++;//深度计数
   	max_depth = max(max_depth,depth);
	for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
        if(vis[v] || v == fa) continue;
		dfs1(v,u,depth+1);
	}
}
int num1[maxn], max_depth1;
void dfs2(int u, int fa, int depth) {//对某颗子树dfs
	num1[depth]++;
	max_depth1 = max(max_depth1,depth);
	for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if(v == fa || vis[v]) continue;
		dfs2(v,u,depth+1);
	}
}

void dfs3(int u, int fa, int depth, int cntedge) {//统计答案的dfs
	if(k & 1) {//如果是边是中心
		int D = max(0,(k/2+1)-depth+1);
        res[cntedge] += num[D] - num1[D];
		if(fa == root) ///当前边是和root连的边时,要算子树u中对当前边的贡献
		   res[cntedge] += num1[k/1+1+1];///之所以后者要+1,时是因为要从当前边开始往下找,而当前边深度对于root来说一定是1;
	} else {//点是中心
        int D = max(0,k/2+1-depth);
		res[u] += num[D] - num1[D];
	}
	
	for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if(v == fa || vis[v]) continue;
		dfs3(v,u,depth+1,i/2);
	}
    
}

void Div(int u) {
	vis[u] = 1;
	max_depth = 0;
	dfs1(u,0,0);
	for(int i = max_depth-1; i >= 0; -- i) num[i] += num[i + 1];
	
	if(k % 2 == 0) res[u] += num[k/2+1]; //跟新分治节点的答案

	for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if(vis[v]) continue;
        max_depth1 = 0;
        dfs2(v,u,1);   //计算当前子树大小
		for(int j = max_depth1-1; j >= 0; -- j) num1[j] += num1[j + 1];
		dfs3(v,u,1,i/2);
		for(int j = max_depth1; j >= 0; -- j) num1[j] = 0;
	}

	for(int i = max_depth; i >= 0; -- i) num[i] = 0;

	for(int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if(vis[v]) continue;
	    MX = 1e9, root = 0;
		now_num = siz[v];
		getroot(v,u);
		Div(root);
	}
}

inline void init() {
	cnt = 1;
	for(int i = 0; i <= n; ++ i) vis[i] = 0, res[i] = 0, num[i] = 0, head[i] = 0;
}

int main() { 
	int T;
	scanf("%d",&T);
	while(T --) {
	    scanf("%d%d",&n,&k);
		init();
		for(int i = 0; i < n - 1; ++ i) {
			int l, r;
			scanf("%d%d",&l,&r);
			add(l,r);
			add(r,l);
		}
		root = 0, MX = 1e9;
        now_num = n;
		getroot(1,0);
		Div(root);
		int ans = 1e9;
		for(int i = 1; i < n; ++ i)
		  ans = min(ans,res[i]);
		if(k % 2 == 0) ans = min(ans,res[n]);//因为边只有n-1条,但是点却又n条。
		printf("%d\n",ans);
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值