[noip2015]运输计划(LCA,二分)

运输计划[做题笔记]


挺难绷的。。。

题意

概括:给定 \(n\) 个节点的树和 \(n-1\) 条边的权值,现在可以将一条边的权值改为 \(0\) 。找出一条边,使得将这条边权值赋为 \(0\) 时,\(T\) 组节点 \(u,v\) 之间的距离最大值最小,输出最小值。

思路分析

一开始想假了,天真的以为被 \(T\) 组节点 \(u,v\) 覆盖的次数最多,且权值较大的边就是要删去的边,\(38\)分,查题解才知道是二分答案。

不过确实,求最大值最小,的确是要二分的。若 \(t1<t2\) ,且 \(t1\) 合法,那么\(t2\)当然合法,单调性不就出来了?明确要二分后,先预处理出 \(T\) 组节点 \(u,v\)\(LCA\) 和距离 \(len\) ,那么答案二分的区间就是 \([0,MAXLEN]\)

二分答案的精髓是什么?是check()函数
————miaomiao

首先为了不产生歧义,我们定义节点\(u,v\)之间的树上路径为链,路径长为链长;下文的边即为树的边。
那么,对于当前 \(check\)\(x\),有:

  • \(First\),要使 \(x\) 合法,那么删去这条边后,所有链长均小于等于 \(x\)
  • \(Second\),要找出这条边,首先要枚举所有的链长 \(len\) ,记录比\(x\)大的个数 \(sum\) ,并将这些链在树上差分。如果有一条边被上述链同时覆盖,显然这就是我们要赋\(0\)的最优边(当然可能存在多条,显然要取边权最大的 \(max\_ len\))
  • \(Third\),用 \(MAXLEN-max\_ len\),这是赋\(0\)后的最长链的长,如果结果\(<=x\),显然\(x\)合法(最长边都满足,底下那些小弟就更不用说了)

嗯,一切都这么美妙,但如果不会树上差分,不就祭了?那么,首先我们要会正常的差分 (不会建议回普及组重修),说白了差分就是一个反映相对大小的东西

如果你觉得一个知识点很简单,下一步,把它挂到树上去。
————波波

定义一个差分数组 \(spx[\ ]\) 。对于这种求边被覆盖的次数,首先边权肯定都是\(0\),边上差分很不方便,那么我们把边权下放到点上,\(spx_i\) 表示 \(i\)\(i\) 的父节点之间的边的差分,当我们要将 \(u_i,v_i\) 这条链放到覆盖到树上,显然我们只需要修改 \(u_i\) 节点, \(v_i\) 节点和 \(LCA(u_i,v_i)\) 的差分,\(spx[u_i]\)++, \(spx[v_i]\)++, \(spx[lca(u_i,v_i)]\)-=\(2\)。为啥是\(2\)?很显然我们将链 \((u_i,v_i)\) 分成了 \((u_i,lca_i)\)\((v_i,lca_i)\) 两条链进行差分,显然要减\(2\)

差分完之后,实际的边权\(k\)就是它的子树中差分数组的和(类似于正常差分中前缀和),这可以通过一遍\(dfs\)实现。

服了,求\(LCA\)必须用\(tarjan\),倍增过不了,卡\(95\)……挺难崩的,现学\(tarjan\)\(LCA\)(狂\(D\)不止)不过\(tarjan\)求确实比倍增快,\(O(n+m)\)\(O(n\ log_2n)\) 友好多了

呃,如果有dalao发现我的代码和您差不多,那我可能是贺的您的,别D我(逃,%%%

\(AC \ code\)

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define N 300010
#define int long long
int n,m;int ans;
struct EDGE{
    int next,to,t;
}e[N<<1],d[N<<1];//q是存原树,d是存查询的T组u,v构成的树,为了tarjan求LCA而生
int spx[N];//这是差分数组,数组名只是数组名,没啥特殊含义
int he[N],tot;
int hd[N];
int edge[N];//树上差分,将边权下放到点上进行差分,edge[i]表示节点i代表的边
int MAXLEN;//最长路径
struct SOLVE{
    int u,v,lca,len;
}q[N];//这个结构体存的是T条链(ui,vi)的信息

inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')  f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')    x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x)
{
    if(x<0)  putchar('-'),x=-x;
    if(x>9)    write(x/10);
    putchar(x%10+'0');
    return;
}

void add(int u,int v,int t)
{
    tot++;
    e[tot].to=v;
    e[tot].next=he[u];
    he[u]=tot;
    e[tot].t=t;
    return ;
}
void add_d(int u,int v)
{
    tot++;
    d[tot].to=v;
    d[tot].next=hd[u];
    hd[u]=tot;
    return;
}
int fa[N];//并查集
int dis[N];//前缀和方便计算链长
int find(int x)
{
    if(x==fa[x])  return x;
    return fa[x]=find(fa[x]);
}
bool f[N];
void tarjan(int x,int pre)//tarjan求LCA
{
    int y;
    for(int i=he[x];i>0;i=e[i].next)
    {
        y=e[i].to;
        if(y!=pre)
        {
            edge[y]=i;
            dis[y]=dis[x]+e[i].t;
            tarjan(y,x);
            int fa_x=find(x);
            int fa_y=find(y);
            if(fa_x!=fa_y)
                fa[fa_y]=fa_x;
            f[y]=1;
        }
    }
    for(int i=hd[x];i>0;i=d[i].next)
    {
        y=d[i].to;
        if(f[y]){
            int now=(i+1)>>1;//因为存的是双向边,所以第i条边对应的编号为(i+1)/2
            q[now].lca=find(y);
            q[now].len=dis[x]+dis[y]-2*dis[q[now].lca];
            MAXLEN=max(MAXLEN,q[now].len);
        }
    }
}
int sum,max_len;//sum代表比当前check的x大的边的个数,即需要减小的边数
void dfs_spx(int x,int pre)//树上差分
{
    int y;
    for(int i=he[x];i>0;i=e[i].next)
    {
        y=e[i].to;
        if(y!=pre)
        {
            dfs_spx(y,x);
            spx[x]+=spx[y];
        }
    }
    if(spx[x]==sum)//如果该边能够让所有链都覆盖
        max_len=max(max_len,e[edge[x]].t);
}
bool check(int x)
{
    for(int i=0;i<=n;i++)  spx[i]=0;
    sum=0;max_len=0;
    for(int i=1;i<=m;i++)
    {
        if(q[i].len>x)
        {
            sum++;
            spx[q[i].u]++;
            spx[q[i].v]++;
            spx[q[i].lca]-=2;
        }
    }
    dfs_spx(1,0);
    if(MAXLEN-max_len<=x)
        return 1;
    return 0;
}

signed main()
{
    n=read();m=read();
    for(int i=1;i<n;++i)
    {
        int a,b,c;
        a=read(),b=read(),c=read();
        add(a,b,c);add(b,a,c);
    }
    tot=0;
    for(int i=1;i<=n;++i)    fa[i]=i;//并查集初始化
    for(int i=1;i<=m;++i)
    {
        int u,v;
        u=read(),v=read();
        q[i].u=u;q[i].v=v;
        add_d(u,v);add_d(v,u);
    }
    tarjan(1,0);
    int st=0,ed=MAXLEN;
    while(st<ed)
    {
        int mid=(st+ed)>>1;
        if(check(mid))    ed=ans=mid;
        else    st=mid+1;
    }
    write(ans);
    return 0;
}

\(Update 5.15\)

貌似树剖求 \(LCA\) 更快,因为它常数小且严格跑不满,所以 \(O(nlogn)\) 此时比 \(O(n+m)\) 还快。。。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 300010
int n,m;
int MAX;
struct EDGE{int next,to,t;}e[N<<1];
int head[N],total;
void add(int u,int v,int t){e[++total]={head[u],v,t};head[u]=total;}
int edge[N];

namespace Tree_Chain_Partition{
    int fa[N],siz[N],wson[N],depth[N];
    int top[N];
    int dis[N];
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            edge[y]=i;
            dis[y]=dis[x]+e[i].t;
            fa[y]=x;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }

    void dfs2(int x,int tp){
        top[x]=tp;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==wson[x]||y==fa[x])  continue;
            dfs2(y,y);
        }
        
    }
    int LCA(int u,int v){
        while(top[u]!=top[v]){
            if(depth[top[u]]<depth[top[v]])  swap(u,v);
            u=fa[top[u]];
        }
        return (depth[u]<depth[v]?u:v);
    }
} using namespace Tree_Chain_Partition;
int u[N],v[N],lca[N],len[N];
int b[N];
int sum,reduce;
void dfs(int x){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa[x])  continue;
        dfs(y);
        b[x]+=b[y];
    }
    if(b[x]==sum)  reduce=max(reduce,e[edge[x]].t);
}

bool check(int x){
    for(int i=1;i<=n;i++)  b[i]=0;
    sum=reduce=0;
    for(int i=1;i<=m;i++){
        if(len[i]>x){
            sum++;
            b[u[i]]++;
            b[v[i]]++;
            b[lca[i]]-=2;
        }
    }
    dfs(1);
    if(MAX-reduce<=x)  return 1;
    return 0;
}

int ans;
signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,m=read;
    for(int a,b,c,i=1;i<n;i++){
        a=read,b=read,c=read;
        add(a,b,c),add(b,a,c);
    }
    dfs1(1),dfs2(1,1);
    for(int i=1;i<=m;i++){
        u[i]=read,v[i]=read;
        lca[i]=LCA(u[i],v[i]);
        len[i]=dis[u[i]]+dis[v[i]]-2*dis[lca[i]];
        MAX=max(MAX,len[i]);
    }
    int st=0,ed=MAX;
    while(st<ed){
        int mid=(st+ed)>>1;
        if(check(mid))
            ed=ans=mid;
        else
            st=mid+1;
    }
    write(ans);
    return 0;
}

posted @ 2024-03-08 18:04  lty_ylzsx  阅读(13)  评论(0编辑  收藏  举报