给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000
树的点分治,用数组cnt[x]记录与根距离为x最小多少条边,然后每搜完一棵子树,再用这棵子树的信息来更新cnt,避免统计同一棵子树中的点对。
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 200005
using namespace std;
struct E{
int to,nxt,d;
}b[maxn<<1];
int fst[maxn],tot=1;
void insert(int f,int t,int d)
{
b[++tot]=(E){t,fst[f],d};fst[f]=tot;
b[++tot]=(E){f,fst[t],d};fst[t]=tot;
}
int inf=1e9;
int n,K,root,sum,ans=1e9;
int mx[maxn],size[maxn];
bool vis[maxn];
void get_root(int x,int f)
{
size[x]=1;mx[x]=0;
for(int i=fst[x];i;i=b[i].nxt)
{
int v=b[i].to;
if(!vis[v]&&f!=v)
{
get_root(v,x);
size[x]+=size[v];
mx[x]=max(mx[x],size[v]);
}
}
mx[x]=max(mx[x],sum-size[x]);
if(mx[x]<mx[root]) root=x;
}
int deep[maxn],dis[maxn];
int cnt[1000005],top,pre;
pair<int,int> a[maxn];
void get_dis(int x,int f)
{
int p=dis[x];
if(p>K) return ;
a[++top]=make_pair(dis[x],deep[x]);
ans=min(ans,deep[x]+cnt[K-p]);
for(int i=fst[x];i;i=b[i].nxt)
{
int v=b[i].to;
if(!vis[v]&&v!=f)
{
dis[v]=dis[x]+b[i].d;
deep[v]=deep[x]+1;
if(dis[v]<=K)
get_dis(v,x);
}
}
}
void calc(int x)
{
dis[x]=0;deep[x]=0;
top=0;pre=1;
for(int i=fst[x];i;i=b[i].nxt)
{
int v=b[i].to;
if(!vis[v])
{
deep[v]=1;dis[v]=b[i].d;
get_dis(v,x);
for(int i=pre;i<=top;i++)
{
int p=a[i].first;
if(p<=K)cnt[p]=min(cnt[p],a[i].second);
}
pre=top+1;
}
}
for(int i=1;i<=top;i++)
{
int p=a[i].first;
if(p<=K) cnt[p]=inf;
}
cnt[0]=0;
}
void solve(int x)
{
calc(x);vis[x]=1;
for(int i=fst[x];i;i=b[i].nxt)
{
int v=b[i].to;
if(!vis[v])
{
sum=size[v];
root=0;
get_root(v,0);
solve(root);
}
}
}
int main()
{
scanf("%d%d",&n,&K);
int u,v,d;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&d);
u++;v++;
insert(u,v,d);
}
mx[0]=maxn;sum=n;
for(int i=1;i<=K;i++)cnt[i]=inf;
get_root(1,0);
solve(root);
if(ans==inf) ans=-1;
printf("%d",ans);
return 0;
}
其他类似点分治题目3365,2152,1468