题目描述
给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000
解析:
点分治模板题,一开始有一个地方理解的不是很好,就是如何保证取的两条路径不在一个子树之内,后来一个很强很帅还能拿AU的学长告诉我可以先遍历子树但不修改子树中的点对答案的贡献,等到遍历完子树后再修改,这样可以保证取的两条路径一定不在一个子树内(因为当你更新答案的时候当前子树内的点对答案的贡献还未修改,自然无法用它来更新答案了)
代码:
#include<iostream>
#include<cstdio>
#define inf 1e9
using namespace std;
struct point
{
int to;
int next;
int dis;
}e[400010];
int n,num,ans,root,sum,k;
int t[1000001],head[200010],son[200010],f[200010],d[200010],dis[200010];
bool vis[200010];
void add(int from,int to,int dis)
{
e[++num].next=head[from];
e[num].to=to;
e[num].dis=dis;
head[from]=num;
}
void getroot(int x,int fa)
{
son[x]=1;
f[x]=0;
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(to==fa||vis[to]) continue;
getroot(to,x);
son[x]+=son[to];
f[x]=max(f[x],son[to]);
}
f[x]=max(f[x],sum-son[x]);
if(f[x]<f[root]) root=x;
}
void cal(int x,int fa)
{
if(dis[x]<=k) ans=min(ans,d[x]+t[k-dis[x]]);
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(to==fa||vis[to]) continue;
d[to]=d[x]+1;
dis[to]=dis[x]+e[i].dis;
cal(to,x);
}
}
void SLR(int x,int fa,bool flag)
{
if(dis[x]<=k)
{
if(flag) t[dis[x]]=min(t[dis[x]],d[x]);
else t[dis[x]]=inf;
}
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(to!=fa&&!vis[to])
SLR(to,x,flag);
}
}
void solve(int x)
{
vis[x]=true;
t[0]=0;
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(vis[to]) continue;
d[to]=1;
dis[to]=e[i].dis;
cal(to,0);
SLR(to,0,1);
}
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(!vis[to])
SLR(to,0,0);
}
for(int i=head[x];i!=0;i=e[i].next)
{
int to=e[i].to;
if(vis[to]) continue;
root=0;
sum=son[to];
getroot(to,0);
solve(root);
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++) t[i]=n;
for(int i=1;i<=n-1;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x++; y++;
add(x,y,z);
add(y,x,z);
}
ans=sum=f[0]=n;
getroot(1,0);
solve(root);
if(ans!=n) printf("%d",ans);
else printf("-1");
return 0;
}