【BZOJ】2599: [IOI2011]Race 点分治
【题意】给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000。注意点从0开始编号,无解输出-1。
【算法】点分治
【题解】每个区域按重心x划分子树,一条经过x的路径是从一个子树到另一个子树的(从x出发记为深度0即可),那么就让子树i的路径与子树1~i-1尝试合并,然后并入,这样就可以找到所有路径。
每个区域记录t[i]表示长度为i的路径最小边数,就可以过程中合并时计算答案ans = min{ ans , deep[x]+t[k-dis[x]] } 。
每个区域处理完要把t[]清空,然后再进入各个子区域。清空不能memset(否则复杂度不对),只能清空访问过的点。
复杂度O(n log n)。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=200010,maxk=1000010; int first[maxn],tot,sz[maxn],n,root,k,sum,dis[maxn],deep[maxn],ans,t[maxk],c[maxn],L,R; bool vis[maxn]; struct edge{int v,w,from;}e[maxn*2]; void insert(int u,int v,int w){tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} void getroot(int x,int fa){ sz[x]=1;bool ok=1; for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa&&!vis[e[i].v]){ getroot(e[i].v,x); sz[x]+=sz[e[i].v]; if(sz[e[i].v]>sum/2)ok=0; } if(sum-sz[x]<=sum/2&&ok)root=x; } void dfs(int x,int fa){ if(dis[x]>k)return; ans=min(ans,deep[x]+t[k-dis[x]]); c[++R]=x; for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa&&!vis[e[i].v]){ dis[e[i].v]=dis[x]+e[i].w; deep[e[i].v]=deep[x]+1; dfs(e[i].v,x); } } void solve(int x,int s){ vis[x]=1;L=0;R=0; for(int i=first[x];i;i=e[i].from)if(!vis[e[i].v]){ deep[e[i].v]=1;dis[e[i].v]=e[i].w; dfs(e[i].v,x); for(int j=L+1;j<=R;j++)t[dis[c[j]]]=min(t[dis[c[j]]],deep[c[j]]); L=R; } for(int i=1;i<=R;i++)t[dis[c[i]]]=n; for(int i=first[x];i;i=e[i].from)if(!vis[e[i].v]){ if(sz[e[i].v]>sz[x])sum=s-sz[x];else sum=sz[e[i].v]; root=e[i].v; getroot(e[i].v,x); solve(root,sum); } } int main(){ scanf("%d%d",&n,&k); int u,v,w; for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&w); u++;v++; insert(u,v,w);insert(v,u,w); } root=1;sum=n;ans=n; for(int i=1;i<=k;i++)t[i]=n; getroot(1,0); solve(root,n); if(ans==n)printf("-1"); else printf("%d",ans); return 0; }