[NOIP2015 提高组] 运输计划题解

[NOIP2015 提高组] 运输计划题解

题目链接:P2680 [NOIP2015 提高组] 运输计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

看了好长时间题解才终于懂的,有关lca和二分答案的题解解释的不详细,一时半会理解不过来,于是自己写一篇解释尽管解释主要在代码中,希望能对迷茫的小伙伴有帮助

解析(主要为二分答案的解析,lca只是求距离和找覆盖边时用得到,这里不多说):

由于m个运输计划是同时出发,所以所需要的时间取决于花费最长的时间,因为一个任务在y分钟内完成,那么另一个任务x(x<=y)也一定完成。

问题就转化成了求最长边最短,二分答案。

接下来,就是怎么二分了

想让时间大于答案的任务的时间减小,题目里说了,可以设一条边为虫洞,我们就找这个任务里哪条(些)边可以设为虫洞,这条(些)边就是被覆盖的边,我们将它们记录一下,所以我们只要找这些超时的任务的公共覆盖边就好了,即这条(些)边被记录的次数等于超时的任务数

代码有详细注释,不懂可以去看一下代码

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
const ll N=3e5+5;
ll n,m,cnt;
ll h[N],lg[N],sum[N],deep[N],st[N][20],vis[N],up[N];
struct edge//
{
    ll v,w,nxt;
} e[N<<1];
struct rw//任务 
{
    ll u,v,lca,dis;
    bool operator <(const rw x)//重载运算符 
    {
        return dis<x.dis;
    }
} g[N];
inline ll read()
{
    ll x=0;
    bool flag=false;
    char ch=getchar();
    while(ch<"0"||ch>"9")
    {
        if(ch=="-")        flag=true;
        ch=getchar();
    }
    while(ch>="0"&&ch<="9")
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return flag?~x+1:x;
}
void add(ll u,ll v,ll w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=h[u];
    h[u]=cnt;
}
void dfs(ll u,ll fat)
{
    deep[u]=deep[fat]+1;//深度更新 
    st[u][0]=fat;//更新st表 
    for(rll i=1;i<=lg[deep[u]];++i)
    {
        st[u][i]=st[st[u][i-1]][i-1];//更新st表 
    }
    for(rll i=h[u];i;i=e[i].nxt)//遍历 
    {
        ll v=e[i].v,w=e[i].w;
        if(v!=fat)//如果不是父节点 
        {
            sum[v]=(sum[u]+w);
            //到根节点的距离就是父节点到根节点的距离加上这条边的边权 
            up[v]=w;//处理到父节点的距离 
            dfs(v,u);//继续搜索 
        }
    }
}
ll LCA(ll x,ll y)//正常求LCA 
{
    if(deep[x]<deep[y])
    {
        x=x^y,y=x^y,x=x^y;//位运算版本的交换 
    }
    while(deep[x]>deep[y])
    {
        x=st[x][lg[deep[x]-deep[y]]-1];//让两点在同一深度 
    }
    if(x==y)    return x;
    for(rll i=lg[deep[x]]-1;i>=0;--i)//找交汇之前最后到达的点 
    {
        if(st[x][i]!=st[y][i])//如果不相交,向上跳 
        {
            x=st[x][i];
            y=st[y][i];
        }
    }
    return st[x][0];//返回lca 
}
bool check(ll len)
{
    if(g[m].dis<=len)    return true;
    //如果路程最长的都比不过当前二分的答案,那么这个答案是可行的,直接返回 
    for(rll i=1;i<=n;++i)    vis[i]=0;//每次操作前,数组清0 
    ll cont=0,maxn=0;
    //cont 记录比当前答案大的边,maxn记录被覆盖次数最多的边被覆盖的次数
    //(我知道maxn这里我解释的很trouble,不好理解就把他看成每条边被覆盖的次数的最大值) 
    //一定要时刻记住maxn的含义!!! 
    for(rll i=m;i>=1;--i)//遍历 
    {
        if(g[i].dis<=len)    break;
        //如果当前任务的路程已经小于答案,就没必要继续遍历查找了,退出即可 
        cont++;//当前任务路程比答案大,计数器+1 
        ll u=g[i].u,v=g[i].v,lca=g[i].lca,c=g[i].dis-len;
        //c 如果这条边大于或等于c,就说明删去它,这个任务的时间就小于或等于答案
        //即这条边可以设为虫洞 
        while(u!=lca)//如果起点不是lca,那就向上找,查询被覆盖的边 
        {
            if(up[u]>=c)//大于c,说明在这个任务中,它可以设为虫洞,也就是上文说的覆盖 
            {
                vis[u]++;
                maxn=max(maxn,vis[u]);//更新maxn 
            }
            u=st[u][0];//往上慢慢找,时间限制是1s~2s 
        }
        while(v!=lca)//如果终点不是lca,向上找 
        {
            if(up[v]>=c)//和上面一样 
            {
                vis[v]++;
                maxn=max(maxn,vis[v]);//更新maxn 
            }
            v=st[v][0];//慢慢跳(~ ̄▽ ̄)~ 
        }
        if(maxn<cont)return false;
        //这里maxn只能小于等于cont,因为进行了cont次循环,一条边最多被覆盖cont次 
        //maxn是边被覆盖的次数的最大值
        //如果小于cont,就说明找不到一条边可以被删去后使任务的时间小于等于答案
        //所以这个答案不成立 
        //如果maxn==cont 就说明至少有一条边可以做到删去后使任务的时间小于等于答案 
    }
    return true;
}
int main()
{
    n=read(),m=read();
    for(rll i=1;i<n;++i)
    {
        ll x=read(),y=read(),z=read();
        add(x,y,z);
        add(y,x,z);
    }
    for(rll i=1;i<=n;++i)
    {
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);//预处理log,节约时间 
    }
    dfs(1,0);//搜索 
    for(rll i=1;i<=m;++i)//记录每一个计划的信息 
    {
        ll u=read(),v=read(),lca=LCA(u,v);
        g[i].u=u;//起点 
        g[i].v=v;//终点 
        g[i].lca=lca;//lca 
        g[i].dis=sum[u]+sum[v]-(sum[lca]<<1);//这个计划的长度 
    }
    sort(g+1,g+m+1);//将任务从小到大排序 
    ll l=0,r=3e8,ans;
    while(l<r)
    //注意!!!这里二分的是最终答案,也就是最小花费时间,清楚概念,否则后边就和我一样容易混 
    {
        ll mid=(l+r)>>1;//二分操作 
        if(check(mid))    r=mid,ans=mid;
        else    l=mid+1;
    }    
    printf("%lld
",ans);
    return 0;
} 

 

原文地址:https://www.cnblogs.com/yifan0305/archive/2022/07/03/16440297.html