洛谷 P2680 [NOIP2015 提高组] 运输计划(二分,树上查分,树上倍增,LCA)
传送门
比较综合的一道题。
解题思路
求把一条边变为0后这m条路径中的最短值,最大值最小,可以二分求解。
如何check某个答案x是否合法?
实质就是判断能否找到一条边,使得大于x的路径都经过这条边,并且减去这条边边权后路径长度都小于等于x。
于是我们先预处理出要求的路径的原长度(倍增求LCA),然后对于每个x,记录下每条边有多少原长超过x的路径经过(树上差分O(n)),再遍历一遍图,看看有没有满足条件的边(标记的经过路径数等于总共路径数量并且边权大于等于最长路径-x),做出判断。
注意事项:卡常。二分的右边界设置为最长路径。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=3e5+7;
int n,m,p[maxn],a[maxn],b[maxn],q[maxn],d[maxn],f[maxn],dp[maxn][21],dis[maxn],len[maxn],lca[maxn],l,r,maxlen,cnt;
struct node{
int v,next,value;
}e[maxn*2];
void insert(int u,int v,int w){
cnt++;
e[cnt].v=v;
e[cnt].value=w;
e[cnt].next=p[u];
p[u]=cnt;
}
void dfs(int u,int fa,int dep,int w){
f[u]=fa;
d[u]=dep;
dis[u]=w;
dp[u][0]=fa;
for(int i=1;(1<<i)<dep;i++){
dp[u][i]=dp[dp[u][i-1]][i-1];
}
for(int i=p[u];i!=-1;i=e[i].next){
if(e[i].v==fa) continue;
dfs(e[i].v,u,dep+1,w+e[i].value);
}
}
int getlca(int x,int y){
if(d[x]<d[y]) swap(x,y);
int t=d[x]-d[y];
for(int i=20;i>=0;i--){
if(t&(1<<i)) x=dp[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(dp[x][i]!=dp[y][i]){
x=dp[x][i];
y=dp[y][i];
}
}
return dp[x][0];
}
void dfs2(int u){
for(int i=p[u];i!=-1;i=e[i].next){
if(e[i].v==f[u]) continue;
dfs2(e[i].v);
q[u]+=q[e[i].v];
}
}
bool check(int x){
memset(q,0,sizeof(q));
int k=maxlen-x,cnt2=0;
for(int i=1;i<=m;i++){
if(len[i]>x){
q[a[i]]++,q[b[i]]++,q[lca[i]]-=2;
cnt2++;
}
}
dfs2(1);
for(int i=2;i<=n;i++){
if(q[i]==cnt2&&dis[i]-dis[f[i]]>=k) return true;
}
return false;
}
int main(){
memset(p,-1,sizeof(p));
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);
insert(v,u,w);
}
for(int i=1;i<=m;i++) scanf("%d%d",&a[i],&b[i]);
dfs(1,-1,1,0);
for(int i=1;i<=m;i++){
lca[i]=getlca(a[i],b[i]);
len[i]=dis[a[i]]+dis[b[i]]-2*dis[lca[i]];
r=max(r,len[i]);
}
l=0;
maxlen=r;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}
//NOIP2015提高组Day2 t3