Jzoj4715 树上路径
给出一棵树,求出最小的k,使得,且在树中存在路径p,使得k>=S且k<=E。(k为路径p上的边的权值和)
这是一道点分治的题目,不过做法多样,也可以用启发式合并水过去,好像还有一种方法是什么二分
如果直接点分治的话,我们将子树内所有的点排序,求出其所处的子树编号belong,再求出next数组,next[i]=j表示belong[i]!=belong[j]的最小的j(i<j),这样的话,我们对于一个i就可以二分答案求出一个j使得v[i]+v[j]>=S的j,若belong[i]=belong[j]则令j=next[j],统计答案即可
#pragma GCC opitmize("O3")
#pragma G++ opitmize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100010
using namespace std;
struct Node{ int len,p; }s[N];
struct Edge{ int v,c,nt; } G[N<<1];
int d[N],sz[N],f[N]={1<<27},vis[N],h[N];
int pnt[N],n,rt,cnt=0,t,nS,nt[N],S,E,A=1<<30;
inline void gmin(int& x,int y){ if(x>y)x=y; }
inline void gmax(int& x,int y){ if(x<y)x=y; }
inline bool c1(Node a,Node b){ return a.len<b.len; }
inline void adj(int x,int y,int c){
G[++cnt]=(Edge){y,c,h[x]}; h[x]=cnt;
}
void gSize(int x,int p){
sz[x]=1;
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v] && v!=p){
gSize(v,x);
sz[x]+=sz[v];
}
}
void gRoot(int x,int p){
sz[x]=1; f[x]=0;
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v] && v!=p){
gRoot(v,x);
sz[x]+=sz[v];
gmax(f[x],sz[v]);
}
gmax(f[x],nS-sz[x]);
if(f[x]<f[rt]) rt=x;
}
void gParent(int x,int p){
s[++t]=(Node){d[x],pnt[x]};
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v] && v!=p){
if(p) pnt[v]=pnt[x];
d[v]=d[x]+G[i].c;
gParent(v,x);
}
}
void gCal(int x){
d[x]=0; t=0; pnt[x]=x;
for(int i=h[x];i;i=G[i].nt) pnt[G[i].v]=G[i].v;
gParent(x,0); sort(s+1,s+1+t,c1); nt[t]=t+1;
for(int i=t-1;i;--i) nt[i]=(s[i].p!=s[i+1].p?i+1:nt[i+1]);
for(int l=1,r=t;l<r;++l){
for(;l<r&&s[r-1].len+s[l].len>=S;--r);
if(s[l].p==s[r].p) r=nt[r];
if(r<=t&&s[l].len+s[r].len>=S) gmin(A,s[l].len+s[r].len);
}
}
void gDfs(int x){
gCal(x); vis[x]=1; gSize(x,0);
for(int v,i=h[x];i;i=G[i].nt)
if(!vis[v=G[i].v]){
rt=0; nS=sz[x];
gRoot(v,x); gDfs(rt);
}
}
int main(){
scanf("%d%d%d",&n,&S,&E);
for(int a,b,c,i=1;i<n;++i){
scanf("%d%d%d",&a,&b,&c);
adj(a,b,c); adj(b,a,c);
}
nS=n; gRoot(1,0); gDfs(rt);
if(A>E) puts("-1"); else printf("%d\n",A);
}
另一种方法是启发式合并,随便选一个根,将每个子树的节点加入数组中让后用归并排序合并,合并前维护一下统计答案
这样好像无论是菊花图还是一条链都是n^2的(雾),但是居然跑得很快
Code来自chengziqi学(ju)长(shen):
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL maxn=2e5+10;
#define INF 1e16
struct edge {LL t,w,next;}e[2*maxn];
LL last[maxn],size[maxn],deep[maxn],f[maxn],g[maxn];
LL n,m,num,cnt,total,root,left,sum,right,h[maxn];
bool vis[maxn];
LL read()
{
LL x=0,f=1; char ch=getchar();
while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
void write(LL x)
{
if (x<0) putchar('-'),x=-x;
if (x>9) write(x/10);
putchar(x%10+'0');
}
void road(LL x,LL y,LL z) {e[++num].t=y; e[num].w=z; e[num].next=last[x]; last[x]=num;}
void get_root(LL x,LL fa)
{
size[x]=1; f[x]=0;
for (int i=last[x];i;i=e[i].next)
{
LL y=e[i].t;
if (vis[y]==true || y==fa)
continue;
get_root(y,x);
size[x]+=size[y];
f[x]=max(f[x],size[y]);
}
f[x]=max(f[x],total-size[x]);
if (f[x]<f[root]) root=x;
}
void merge_sort(LL l,LL r,LL mid)
{
LL s=l,t=mid+1,k=l-1;
for (int i=l;i<=r;i++) h[i]=g[i];
while (s<=mid && t<=r)
if (h[s]<h[t]) g[++k]=h[s++];
else g[++k]=h[t++];
while (s<=mid) g[++k]=h[s++];
while (t<=r) g[++k]=h[t++];
}
void work(LL x,LL fa)
{
g[++cnt]=0; LL k=cnt+1,t=0;
for (int i=last[x];i;i=e[i].next)
{
LL y=e[i].t;
if (y==fa) continue;
t=cnt+1; work(y,x);
for (int j=t;j<=cnt;j++)
{
g[j]+=e[i].w;
if (g[j]>=left && g[j]<=right)
sum=min(sum,g[j]);
if (g[j]>right)
{cnt=j-1; break;}
}
LL s=cnt;
if (s<t) continue;
for (int j=k;j<t;j++)
{
while (g[j]+g[s]>=left && s>t) s--;
if (g[j]+g[s]<left && s<cnt) s++;
if (g[j]+g[s]>=left)
sum=min(sum,g[j]+g[s]);
if (s==t && h[s]+h[j]>=left) break;
}
if (k<t) merge_sort(k,cnt,t-1);
}
}
int main()
{
root=sum=num=cnt=0; f[0]=INF;
memset(vis,false,sizeof(vis));
n=read(); left=read(); right=read();
for (int i=1;i<n;i++)
{
LL x=read(),y=read(),z=read();
road(x,y,z); road(y,x,z);
}
total=n; get_root(1,0);
sum=INF; work(root,0);
if (sum==INF) write(-1);
else write(sum);
putchar('\n'); return 0;
}
至于题解里面说的二分我没有看懂,可以参考其他神犇的博客