题面:
给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000
题目分析:
点分治模板题。
解决一个重心的流程大概如下:
(假设当前子树的重心为u,解决u子树内的子问题,
f
[
i
]
f[i]
f[i]表示权值为i的最小边数)
- 遍历u的儿子v1,把对应的边数和权值放入栈中(边数>ans或权值>k return),并用边数+f[k-权值]更新答案。
- 在遍历结束后用栈中的元素更新f数组,继续下一个儿子的遍历。
- 所有儿子遍历结束后把所有栈中元素对应的f置为n,表示消除影响。
Code:
#include<cstdio>
#include<cctype>
#include<algorithm>
#define maxn 200005
using namespace std;
char cb[1<<16],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<16,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,k,siz[maxn],f[1000005],ans=maxn,st[maxn][2],top;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
bool ban[maxn];
inline void line(int x,int y,int z){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;w[tot]=z;}
void calc(int u,int pre,int dep,int dis){
if(dis>k||dep>=ans) return;
ans=min(ans,dep+f[k-dis]);
st[++top][0]=dep,st[top][1]=dis;
for(int i=fir[u];i;i=nxt[i]) if(!ban[to[i]]&&to[i]!=pre) calc(to[i],u,dep+1,dis+w[i]);
}
void solve(int u,int pre){
for(int i=fir[u],v;i;i=nxt[i]) if(!ban[v=to[i]]&&v!=pre){
int cur=top;
calc(v,u,1,w[i]);
for(int j=cur+1;j<=top;j++) f[st[j][1]]=min(f[st[j][1]],st[j][0]);
}
while(top) f[st[top--][1]]=n;
}
void getroot(int u,int pre,int tsz,int &G){
siz[u]=1; bool flg=1;
for(int i=fir[u],v;i;i=nxt[i]) if(!ban[v=to[i]]&&v!=pre){
getroot(v,u,tsz,G),siz[u]+=siz[v];
if(siz[v]<<1 > tsz) flg=0;
}
if((tsz-siz[u])<<1 > tsz) flg=0;
if(flg) G=u;
}
void TDC(int u,int tsz){
getroot(u,0,tsz,u);
solve(u,0); ban[u]=1;
for(int i=fir[u];i;i=nxt[i])
if(!ban[to[i]]) TDC(to[i],siz[to[i]]<siz[u]?siz[to[i]]:tsz-siz[u]);
}
int main()
{
read(n),read(k);
for(int i=1,x,y,z;i<n;i++) read(x),read(y),read(z),x++,y++,line(x,y,z),line(y,x,z);
for(int i=1;i<=k;i++) f[i]=n;
TDC(1,n);
printf("%d",ans<n?ans:-1);
}