P4149 [IOI2011]Race(点分治变式题)

本文介绍了一种利用树上启发式合并(DSU on Tree)解决寻找一条简单路径,其边的权值和等于给定值k且边数量最小的问题。通过递归和分治策略,结合树的重心性质,实现了高效求解。代码中展示了具体的算法实现,包括添加边、获取树的重心、计算路径边数等关键步骤。最后,通过示例数据验证了算法的正确性。
摘要由CSDN通过智能技术生成

题意:

给一棵树,每条边有权。求一条简单路径,权值和等于 k,且边的数量最小。

类似模板题,但也有一点不同,变式吧,手打几遍,熟练一下

这道题也可以用树上启发式合并(DSU on Tree)

有一篇很好的博文,全英文,还在研究

dsu on tree

const int inf=0x3f3f3f3f;
const int maxn=1e6+7;
int n,m,tot,head[maxn],d[maxn],ans;
int sum,s[maxn],cnt[maxn],rt;
int a[maxn],f[maxn],b[maxn],q[maxn];
bool vis[maxn];
struct edge{	int v,w,next;	}e[maxn<<1];
void add(int u,int v,int w){
	e[++tot].v=v;	e[tot].w=w;
	e[tot].next=head[u];	head[u]=tot;
}
void getrt(int u,int k){
	s[u]=1;	cnt[u]=0;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==k||vis[v])	continue;
		getrt(v,u);
		s[u]+=s[v];
		cnt[u]=max(cnt[u],s[v]);
	}
	cnt[u]=max(cnt[u],sum-s[u]);
	if(cnt[u]<cnt[rt])	rt=u;
}
//找重心的同时记录边数 
void getdis(int u,int k,int step){
	if(d[u]>m)	return;			//剪枝 
	a[++a[0]]=d[u];
	//根据现有的距离,与之前的距离比较,更新答案 
	ans=min(ans,f[m-d[u]]+step);
	//数组q[]暂存各距离的边数 
	q[a[0]]=step;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==k||vis[v])	continue;
		d[v]=d[u]+e[i].w;
		getdis(v,u,step+1);
	}
}
void cc(int u){
	int tmp=0;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(vis[v])	continue;
		//初始化距离,并得到该子树中所有可能的距离
		a[0]=0;	d[v]=e[i].w;
		getdis(v,u,1);
		//用临时数组b[]暂存各路径,并更新到各距离的最小边数 
		for(int j=1;j<=a[0];j++){ 
			b[tmp++]=a[j];
			f[a[j]]=min(f[a[j]],q[j]);
		}
	}
	//分治的清零不能用memset
	for(int i=0;i<tmp;i++)	f[b[i]]=inf;
}
void solve(int u){
	vis[u]=1;	f[0]=0;
	cc(u);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(vis[v])	continue;
		cnt[rt=0]=sum=s[v];
		getrt(v,0);
		solve(rt);
	}
}

int main(){
	n=read();	m=read();
	memset(f,inf,sizeof(f));	ans=inf;
	for(int i=1;i<n;i++){
		int u,v,w;	u=read()+1;	v=read()+1;	w=read();
		add(u,v,w);	add(v,u,w);
	}
	cnt[rt]=sum=n;	getrt(1,0);
	solve(rt);
	if(ans==inf)	cout<<-1;
	else	cout<<ans;
}

/*
4 3
0 1 1
1 2 2
1 3 4

2
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值