bzoj2599: [IOI2011]Race

2599: [IOI2011]Race

Time Limit: 70 Sec   Memory Limit: 128 MB
Submit: 2933   Solved: 855
[ Submit][ Status][ Discuss]

Description

给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000

Input

第一行 两个整数 n, k
第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始)

Output

一个整数 表示最小边数量 如果不存在这样的路径 输出-1

Sample Input

4 3
0 1 1
1 2 2
1 3 4

Sample Output

2

HINT

Source

(

update:

注意!!!!:编号从0开始,可能有0权边,所以置inf时要注意不要修改t[0]!!!!!!

)




挑战一下比较有难度的点分治...

这题看似是另一种特殊的点分治套路。

记录t数组,其中t[i]表示长度为i的路径的最小边数。

处理一颗以u为根节点的子树时,对子树u的子树,按序(程序实现我用链式前向星的序,其实只要不重复都无所谓)遍历,遍历到子树v时,用前面已经遍历到的子树更新出的t数组去统计更新ans,之后恢复t数组无穷大的初值。这样就保证了统计到所有的答案且不重复计算。


get新套路:对于不满足前缀减法的点分治可做题,我们可以用按序遍历的方式来统计子树答案,而非容斥加减来做。


附代码:

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<climits>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define N 1000010
#define M 2000020
#define inf 1<<26
using namespace std;
int n,k;
int head[N],pos;bool vis[N];
struct edge{int to,next,c;}e[M];
void add(int a,int b,int c)
{pos++;e[pos].to=b,e[pos].c=c,e[pos].next=head[a],head[a]=pos;}
void insert(int a,int b,int c){add(a,b,c);add(b,a,c);}
int size[N],f[N],rt,sum;
void find_root(int u,int fa)
{
	size[u]=1,f[u]=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa||vis[v])continue;
		find_root(v,u);size[u]+=size[v];
		f[u]=max(f[u],size[v]);
	}f[u]=max(f[u],sum-size[u]);
	if(f[u]<f[rt])rt=u;
}
int t[N*10],ans,d[N],dis[N];
void cal(int u,int fa)
{
	if(dis[u]<=k)ans=min(ans,d[u]+t[k-dis[u]]);
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v]||v==fa)continue;
		dis[v]=dis[u]+e[i].c;
		d[v]=d[u]+1;cal(v,u);
	}
}
void add1(int u,int fa,bool flag)
{
	if(dis[u]<=k)
	{
		if(flag)t[dis[u]]=min(t[dis[u]],d[u]);
		else t[dis[u]]=inf;
	}	
	for(int i=head[u];i;i=e[i].next)
		if(!vis[e[i].to]&&e[i].to!=fa)
			add1(e[i].to,u,flag);
}
void work(int u)
{
	vis[u]=1;t[0]=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v])continue;
		d[v]=1,dis[v]=e[i].c;
		cal(v,0);add1(v,0,1);
	}
	for(int i=head[u];i;i=e[i].next)
		if(!vis[e[i].to])
			add1(e[i].to,0,0);
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;;
		if(vis[v])continue;
		sum=size[v],rt=0;
		find_root(v,0);
		work(rt);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++,y++;insert(x,y,z);
	}f[0]=sum=ans=n;find_root(1,0);
	for(int i=1;i<=k;i++)t[i]=n;
	work(rt);printf("%d\n",ans==n?-1:ans);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值