[luogu-2680]noip2015day2-T3 运输计划 题解

9 篇文章 0 订阅
5 篇文章 0 订阅

题目传送门
题意解析:题目给了你有n个点的一棵树,然后给了你m个询问,询问两个点之间的距离。你可以让一条边的长度变成0,并且这条边是对于所有的询问都是一样的,求最后使一条边变成0后,询问的答案的最大值最小。


My opinion:这题目因为是一棵树,我们就可以预处理出每组询问在没有去边之前的答案,可以用lca求出两个点的最近公共祖先,再用u->v的距离=dis[u]+dis[v]-2*dis[p](dis[x]是表示点x到根的距离,p是u和v的最近公共祖先)预处理出答案,时间复杂度为O(m*lg(n))(如果用st表求lca的话)。之后就是去掉哪一条边的问题了,一开始的想法就是枚举去掉的边,然后对于每组询问求解是否有这条边,然后去掉,之后……就完美的TLE了。该怎么办呢?按照一dalao的说法,我们可以将这个求解性问题转换成判断性问题,使用二分答案法来求答案,然后判断。
总结:
1、用lca求出每组询问的答案。
2、二分答案。
3、判断是否可行。
4、输出答案。
判断方法为:
对于答案ans,记录距离>ans的点对个数s1和最大差值为s2,对于每条不合法的路径,维护每个点的经过次数,然后枚举点,如果经过次数=s1,就尝试将它建成虫洞,如果它对应的边的权值>=s2,那么ans就合法。如果所有点都不行,则ans不合法。
关键是统计每个点在非法路径中的经过次数。我们可以维护sum数组,对于每条非法的路径(u,v),sum[u]++,sum[v]++,sum[lca(u,v)]-=2,这样最后sum[u]+=sum[u的所有儿子],就实现了每个点的经过次数的统计。
判断方法转自某dalao的博客


相比起来超长的代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=a;i>=n;i--)
#define Clear(a,x) memset(a,x,sizeof(a))
#define ll long long
#define INF 2000000000
#define eps 1e-8
using namespace std;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9') f=ch=='-'?-1:f,ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int maxn=300005;
int n,m,len,Max_road;
bool flag[maxn];
int head[maxn],vet[maxn<<1],Next[maxn<<1],road[maxn<<1];
int size[maxn],dep[maxn],father[maxn];
int l[maxn],r[maxn],p[maxn],dis[maxn],s[maxn];
int fa[maxn][20];
void add(int u,int v,int w){
    vet[++len]=v;
    Next[len]=head[u];
    head[u]=len;
    road[len]=w;
}
void dfs(int u){
    flag[u]=0;
    int len=1;
    while ((1<<len)<size[u]){
        fa[u][len]=fa[fa[u][len-1]][len-1];
        len++;
    }
    for (int e=head[u];e;e=Next[e]){
        int v=vet[e];
        if (v!=fa[u][0]){
            dep[v]=dep[u]+road[e];
            size[v]=size[u]+1;
            fa[v][0]=u;
            father[v]=road[e];
            dfs(v);
        }
    }
}
void DFS(int u){
    flag[u]=0;
    for (int e=head[u];e;e=Next[e]){
        int v=vet[e];
        if (flag[v]){
            DFS(v);
            s[u]+=s[v];
        }
    }
}
int lca(int l,int r){
    if (size[l]<size[r]) swap(l,r);
    per(i,19,0)
        if (size[l]-size[r]>=(1<<i)) l=fa[l][i];
    if (l==r) return l;
    per(i,19,0)
        if (fa[l][i]!=fa[r][i]){
            l=fa[l][i];
            r=fa[r][i];
        }
    return fa[l][0];
}
bool check(int mid){
    Clear(s,0);
    Clear(flag,1);
    int ans=0,max1=0,max2=0;
    rep(i,1,m){
        if (dis[i]<=mid) continue;
        s[l[i]]++,s[r[i]]++;
        s[p[i]]-=2;
        ans++;
        max1=max(max1,dis[i]);
    }
    DFS(1);
    rep(i,1,n)
        if (s[i]==ans) max2=max(max2,father[i]);
    if (max1-max2<=mid) return false;
        else return true;
}
int main(){
    n=read(),m=read();
    rep(i,1,n-1){
        int u=read(),v=read(),w=read();
        add(u,v,w);
        add(v,u,w);
    }
    Clear(flag,1);
    dfs(1);
    rep(i,1,m){
        l[i]=read(),r[i]=read();
        int x=lca(l[i],r[i]);
        p[i]=x;
        dis[i]=dep[l[i]]+dep[r[i]]-2*dep[x];
        Max_road=max(Max_road,dis[i]);
    }
    int L=0,R=Max_road;
    while (L<R){
        int mid=(L+R)>>1;
        if (check(mid)) L=mid+1;
            else R=mid;
    }
    printf("%d\n",R);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值