计划运输(noip2015 D2 T3)
题意:
一棵n个节点的树,对于树上的m条链
我们可以选取树上的唯一一条边使它的边权变为0
求处理后最长链的长度
思路:
二分答案:在check函数里枚举所有比二分的mid值大的询问,也就是不满足的当前猜测的答案的询问。
然后对这些询问(是一条条链)都求出公共边,如果去掉这条边可以使不满足的询问满足,就二分猜更小的值找。
找公共边:利用树上差分找被经过的边的次数,树上差分操作:对于每一条路径,将起点和终点-1,它们的lca+2,求lca的子树和即可求出lca上的一条边被经过的次数。
找lca:倍增
#include<bits/stdc++.h> using namespace std; const int N=1e5*3+5; int to[N<<1],head[N],nex[N<<1],we[N<<1],fa[N],dep[N],tot=0; int f[N][22],d[N],x[N],y[N],n,m,num[N],cnt=0,tmp[N]; struct node{ int x,y,d; }e[N]; struct node1{ int x,y,lc,dis; }b[N]; void add(int a,int b,int ww) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; we[tot]=ww; } int read() { int x=0,f=1; char s=getchar(); while(s<'0'||s>'9') { if(s=='-') f=-1; s=getchar(); } while(s>='0'&&s<='9') { x=x*10+s-'0'; s=getchar(); } return x*f; } void dfs(int u) { num[++cnt]=u; for(int i=head[u];i;i=nex[i]) { int v=to[i]; if(v==fa[u]) continue; fa[v]=u; dep[v]=dep[u]+1; d[v]=d[u]+we[i];//更新到根节点的距离 f[v][0]=u; for(int i=1;i<=20;++i) f[v][i]=f[f[v][i-1]][i-1]; dfs(v); } } int lca(int a,int b) { if(dep[a]<dep[b]) swap(a,b); for(int i=20;i>=0;--i) if(dep[f[a][i]]>=dep[b]) a=f[a][i]; if(a==b) return a; for(int i=20;i>=0;--i) if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i]; return f[a][0]; } bool check(int mid) { int ans=0,cnt=0; memset(tmp,0,sizeof(tmp)); for(int i=1;i<=m;i++) if(b[i].dis>mid) {//树上差分:儿子节点++ 父节点-=2 //求任意一子树和就可以得出经过公共点上面那一条边的次数 tmp[b[i].x]++,tmp[b[i].y]++,tmp[b[i].lc]-=2; ans=max(ans,b[i].dis-mid); cnt++; } if(cnt==0) return true; //统计子树和 倒着统计是因为dfs序越大就越深 从儿子向上更新 for(int i=n;i>=1;--i) tmp[f[num[i]][0]]+=tmp[num[i]]; for(int i=2;i<=n;i++)//1是根节点 不循环 //所有询问必须满足条件所要减掉的公共边 //减掉之后满足二分的答案 就 return true if(tmp[i]==cnt&&d[i]-d[f[i][0]]>=ans) return true; return false; } int main() { freopen("transport.in","r",stdin); freopen("transport.out","w",stdout); int sum=0,minn=1005; n=read(),m=read(); for(int i=1;i<=n-1;++i) { e[i].x=read(),e[i].y=read(),e[i].d=read(); add(e[i].x,e[i].y,e[i].d),add(e[i].y,e[i].x,e[i].d); minn=max(minn,e[i].d); } dep[1]=1; dfs(1); for(int i=1;i<=m;i++) { b[i].x=read(),b[i].y=read(); b[i].lc=lca(b[i].x,b[i].y); b[i].dis=d[b[i].x]+d[b[i].y]-2*d[b[i].lc]; sum=max(sum,b[i].dis); } //二分思路:二分一个答案 统计出大于这个答案的所有询问 //考虑去掉一条边能不能使所有不满足的满足 //不需要枚举所有都满足 只需要使最大的减去边满足就可以了 int l=sum-minn-1,r=sum+1,ans;//注意二分的边界 否则会超时 while(l<r) { int mid=(l+r)>>1; if(check(mid)) r=mid,ans=mid; else l=mid+1; } printf("%d\n",ans); } /* 6 3 1 2 3 1 6 4 3 1 7 4 3 6 3 5 5 3 6 2 5 4 5 */
浙公网安备 33010602011771号