【CF791D】tree 柠檬树

33 篇文章 0 订阅
24 篇文章 1 订阅

【题目描述】
Herobrine能掌控所有,除了他内心的那棵柠檬树。
他每看到一件让自己心生羡慕的事,他内心的柠檬树上就会多长出一只多汁美味的柠檬。
现在,Herobrine有一棵含有n只柠檬的柠檬树,编号从1到n。这n只柠檬由n-1条树枝相连。 柠檬之间很喜欢用脱落酸进行交流。脱落酸只能通过树枝传递。柠檬们为了尽量频繁的进行交流,就团结一心,调整了树枝的形态,使得任意两只不同的柠檬之间都有且仅有一条传递脱落酸的通路。
我们知道柠檬树上的柠檬和Herobrine有着深仇大恨。这也就是为什么柠檬树上的柠檬要尽可能地进行交流——交流时,脱落酸的传输会消去Herobrine的头发,使得他看起来很难看。柠檬们很喜欢这样。但是存在一个特例。Herobrine告诉你,若有两只柠檬的编号为a和b,且a<b,那么a将脱落酸传输给b时,Herobrine的头发会消去,但当b将脱落酸传输给a时,Herobrine的头发不会消去。
Herobrine想知道一下柠檬树对他头发的威力。Herobrine又告诉你,不仅任意两只柠檬都能进行交流,而且两只柠檬在交流的过程中,脱落酸每传播一段路程,就会使Herobrine掉一根头发。Herobrine告诉你一个常数,叫卡常数k。这意味着,每当脱落酸传播k根树枝时,Herobrine的头发就会掉一根。
你是个好问的孩子。你会问,如果脱落酸传播了k-1根树枝,头发会掉吗?
“废话!你看看我的头顶!当然了!”
Herobrine告诉你一个简洁的方法,用来判断一次交流会掉几根头发。如果脱落酸传播了m根树枝,Herobrine就会掉 ⌈ m k ⌉ \left \lceil \frac{m}{k} \right \rceil km根头发。
“看!柠檬们都开始交流了!”Herobrine痛苦地捂住他那像做过817次化疗后的头顶。现在,**每只柠檬都会和其它所有的柠檬通过树枝用脱落酸进行交流。**那么,有多少次交流会使得Herobrine掉发呢?
【输入格式】
输入第一行包括三个用空格隔开的整数case,n,k。case代表测试数据编号。n,k的含义见题目描述。
输入第二行至第n行,每行包括两个用空格分开的整数a,b,表示柠檬a与柠檬b之间有树枝相连。
【输出格式】
输出一行一个整数,表示会使得Herobrine掉发的交流的次数。
【样例输入1】
1 6 2
1 2
1 3
2 4
2 5
4 6
【样例输出1】
20
【Hint】
n<=2e5,k<=5
—【感谢excitedfrog友情编题】

【分析】
接近于求树上路径和,但对路径和进行了一些奇怪的处理,但是依然可以用点分治解决。这里由于k非常小,所以点分治的合并操作时可以通过枚举余数来进行高效的合并。(凭着残缺的记忆,我竟然首次把点分治打对了,虽然合并时出了锅,跑)
【code】

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int casex,n,k;
ll ans;
int num,hed[150010],nex[300010],vt[300010];
int vi[150010],sum[150010],mx[150010],sumx,root;
ll dep[150010],nn[10],le[10],ccc;

int read(){
	int u=0;char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') u=(u<<1)+(u<<3)+c-'0',c=getchar();
	return u;
}

void add(int x,int y){
	vt[++num]=y,nex[num]=hed[x],hed[x]=num;
	vt[++num]=x,nex[num]=hed[y],hed[y]=num;
}

void get_root(int u,int fa){
	sum[u]=0,mx[u]=0;
	for(int i=hed[u];i;i=nex[i]){
		if(vt[i]==fa||vi[vt[i]]) continue;
		get_root(vt[i],u);
		sum[u]+=sum[vt[i]],mx[u]=max(mx[u],sum[vt[i]]);
	}
	sum[u]++,mx[u]=max(mx[u],sumx-sum[u]);
	if(mx[u]<mx[root]) root=u;
}

void get_dep(int u,int fa,int l){
	dep[++ccc]=l;
	for(int i=hed[u];i;i=nex[i]){
		if(vi[vt[i]]||vt[i]==fa) continue;
		get_dep(vt[i],u,l+1);
	}
}

ll get_ans(int u,int l){
	ll res=0;
	ccc=0;
	get_dep(u,0,l);
	memset(nn,0,sizeof(nn)),memset(le,0,sizeof(le));
	for(int i=1;i<=ccc;i++) le[dep[i]%k]+=dep[i]/k,nn[dep[i]%k]++;
	for(int i=0;i<=k;i++){
		if(i!=0) res+=(i*2>k?nn[i]*(nn[i]-1):nn[i]*(nn[i]-1)/2);
		res+=le[i]*(nn[i]-1);
		for(int j=i+1;j<=k;j++) res+=(i+j>k?nn[i]*nn[j]*2:nn[i]*nn[j])+le[i]*nn[j]+le[j]*nn[i];
	}
	return res;
}

void dfs(int u){
	vi[u]=1,ans+=get_ans(u,0);
	for(int i=hed[u];i;i=nex[i]){
		if(vi[vt[i]]) continue;
		ans-=get_ans(vt[i],1);
		sumx=sum[vt[i]],root=0,get_root(vt[i],u),dfs(root);
	}
}

int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	casex=read(),n=read(),k=read();mx[0]=n;
	for(int i=1;i<=n-1;i++){
		int x=read(),y=read();
		add(x,y);
	}
	sumx=n;
	get_root(1,0);
	dfs(root);
	printf("%lld",ans);
}

【另一个解法】树形dp一维记节点,二维记余数,容斥一下,复杂度nk。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值