NOIP 2015 运输计划

本文介绍了一种结合二分查找、最近公共祖先(LCA)算法及树上差分技巧解决路径查询问题的方法。通过使用Tarjan算法求LCA,并利用树上差分进行路径交点判断,实现对每条路径最大长度的有效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

评测传送

二分答案+LCA+树上差分

最好用Tarjan求LCA,有的oj会卡倍增。

做法:
我们用LCA求出需要查询的每个计划的路径长度。
然后二分答案,check( )的时候,我们把大于mid的路径(因为这一些都是要去边的)求一下交点(边),
如果并非全都都交于一条边或者去掉交边后也不能让这些路径都小于等于mid,那么mid就是不可以的。
求交边时,用到树上差分。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int N=300009;
int head[N],nxt[2*N],to[2*N],w[2*N],tot;
int qh[N],qn[2*N],qt[2*N],qtt,lca[2*N];
bool vis[N];
int f[N],dep[N],a[N],b[N],dis[N],s[N],v[N];
int n,m;
void add(int x,int y,int z)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
    w[tot]=z;
} 
void add2(int x,int y)
{
    qt[++tot]=y;
    qn[tot]=qh[x];
    qh[x]=tot;
}
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]); 
} 
void dfs(int x)//Tarjan找LCA 
{
    vis[x]=1;
    f[x]=x;
    for(int i=head[x];i;i=nxt[i])
    {
        if(!vis[to[i]])
        {
            dep[to[i]]=dep[x]+w[i];
            dfs(to[i]);
            f[to[i]]=x;
            v[to[i]]=w[i];
        } 
    } 
    for(int i=qh[x];i;i=qn[i])
    {
        if(vis[qt[i]])
        {
            lca[i]=find(qt[i]);
            if(i%2) lca[i+1]=lca[i];
            else lca[i-1]=lca[i];
        }
    }
}
void sum(int father,int x)//树上差分
{
    for(int i=head[x];i;i=nxt[i])
    {
        if(to[i]!=father)
        {
            sum(x,to[i]);
            s[x]+=s[to[i]];//s[x]表示的是x这个点与它的儿子的边在路径中出现的次数
        }
    }
}
bool check(int x)
{
    int cnt=0,d=0;//d表示需要去掉的最大长度 
    memset(s,0,sizeof(s));
    for(int i=1;i<=m;i++)
    {
        if(dis[i]>x){
            cnt++;
            d=max(d,dis[i]-x);
            s[a[i]]++;
            s[b[i]]++;
            s[lca[i*2]]-=2;//树上差分
        }
    } 

    sum(1,1);

    for(int i=1;i<=n;i++)
     if(s[i]==cnt&&v[i]>=d) return 1;
    return 0;
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }

    tot=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i],&b[i]);
        add2(a[i],b[i]);add2(b[i],a[i]);
    }

    dfs(1);

    for(int i=1;i<=m;i++)
     dis[i]=dep[a[i]]+dep[b[i]]-2*dep[lca[i*2]];//第i个计划两个城市的距离 

    int L=0,R=N*1000,mid;
    while(L<=R)
    {
        mid=(L+R)>>1;
        if(check(mid)) R=mid-1;
        else L=mid+1;
    }
    printf("%d\n",L);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值