20191212 C 简单点分治

这是一篇关于利用C语言解决树上路径价值问题的博客。题目要求找出所有路径价值为给定倍数P的简单路径数量,其中路径价值等于点权和减去最大点权。通过点分治和路径最大值排序,配合子树内的去重策略,可以求得答案。此题具有较高的智力挑战性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一棵有n个节点的无根树,树上的每个点有一个非负整数点权。定义一条路径的价值为路径上的点权和-路径上的点权最大值。 给定参数PPP,我们想知道,有多少不同的树上简单路径,满足它的价值恰好是PPP的倍数。 注意:单点算作一条路径;u!=vu!=vu!=v时,(u,v)(u,v)(u,v)(v,u)(v,u)(v,u)只算一次。

n<=1e5,P<=1e7n<=1e5 , P<=1e7n<=1e5,P<=1e7

这个,智商压制题。
其实只需要点分治之后按路径最大值排序,再来个简单的子树内去重即可。

AC Code\rm AC\ CodeAC Code

#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;

int n,P,a[maxn];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e=0;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }

int mn,rt,vis[maxn],sz[maxn];

void dfs(int u,int ff,int tsz){
	int mx=0;sz[u]=1;
	for(int i=info[u],v;i;i=Prev[i]) if((v=to[i])!=ff && !vis[v]) dfs(v,u,tsz),mx=max(mx,sz[v]),sz[u]+=sz[v];
	if((mx=max(mx,tsz-sz[u]))<mn) mn=mx,rt=u;
}
int gert(int u,int tsz){ return mn=0x3f3f3f3f,dfs(u,0,tsz),rt; }

LL ans = 0;
int c[maxn],Mx[maxn],Sm[maxn],cnt[10000007];
bool cmp(const int &u,const int &v){ return Mx[u]<Mx[v]; }
void ser(int u,int ff,int mx,int sm){
	mx=max(mx,a[u]),sm=(sm+a[u])%P;
	Mx[++c[0]]=mx,Sm[c[0]]=sm,sz[u]=1,c[c[0]]=c[0];
	for(int i=info[u],v;i;i=Prev[i]) 
		if((v=to[i])!=ff && !vis[v]) 
			ser(v,u,mx,sm),
			sz[u]+=sz[v];
}
void solve(int u){
	c[0]=0,ser(u,0,0,0),sort(c+1,c+1+c[0],cmp);
	for(int i=1;i<=c[0];i++) 
		ans += cnt[(P-(Sm[c[i]]-a[u]-Mx[c[i]])%P)%P],
		cnt[(Sm[c[i]]%P+P)%P]++;
	for(int i=1;i<=c[0];i++) 
		cnt[(Sm[c[i]]%P+P)%P]=0;
	vis[u]=1;
	for(int i=info[u],v;i;i=Prev[i]) 
		if(!vis[v=to[i]]){
		c[0]=0,ser(v,u,0,0),sort(c+1,c+1+c[0],cmp);
		for(int i=1;i<=c[0];i++) 
			ans -= cnt[(P-(Sm[c[i]]+a[u]-Mx[c[i]])%P)%P],
			cnt[(Sm[c[i]]%P+P)%P]++;
		for(int i=1;i<=c[0];i++) 
			cnt[(Sm[c[i]]%P+P)%P]=0;
	}
	for(int i=info[u],v;i;i=Prev[i]) 
		if(!vis[v=to[i]]) solve(gert(v,sz[v]));
}

int main(){
	scanf("%d%d",&n,&P);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),Node(u,v),Node(v,u);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	solve(gert(1,n));
	printf("%lld\n",ans+n);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值