落谷 P4374 (upc 6346) [USACO18OPEN] Disruption

题目描述

Farmer John自豪于他所经营的交通发达的的农场。这个农场是由 NN 块牧场( 2 \leq N \leq 50,0002≤N≤50,000 )组成的, N-1N−1 条双向道路将它们连接起来,每一条道路的都为一单位长度。Farmer John注意到,从任何一块牧场到另一块牧场,都能通过一组合适的道路到达。

尽管FJ的农场现在是连通的,他担心如果有一条道路被阻断会发生什么,因为这事实上会将他的农场分为两个不相交的牧场集合,奶牛们只能够在每一个集合内移动但不能在集合间移动。于是FJ又建造了 MM 条额外的双向道路( 1 \leq M \leq 50,0001≤M≤50,000 ),每一条的长度都是一个至多为 10^9109 的正整数。奶牛们仍然可以使用原有的道路进行移动,除非其中的某些被阻断了。

如果某条原有的道路被阻断了,农场就会被分为两块不相交的区域,那么FJ就会从他的额外修建的道路中选择一条能够重建这两块区域的连通性的,取代原来那条,从而奶牛们又可以从任何一块牧场去往另一块牧场。

对于农场上每一条原有的道路,帮助FJ选出最短的替代用的道路。

输入输出格式

输入格式:

输入的第一行包含 NN 和 MM 。接下来的 N-1N−1 行,每行用整数 pp 和 qq 描述了一条原有的道路,其中 p \neq qp≠q 是这条道路连接的两块牧场(在 1 \ldots N1…N 范围内)。剩下的 MM 行,每行用三个整数 pp 、 qq 和 rr 描述了一条额外的道路,其中 rr 是这条道路的长度。任何两块牧场之间至多只有一条道路。

输出格式:

对原有的 N-1N−1 条道路的每一条,按照它们在输入中出现的顺序,输出如果这条道路被阻断的话,能够重新连接农场的最短的替代用道路的长度。如果不存在合适的替代用的道路,输出-1。

输入输出样例

输入样例#1: 复制

6 3
1 2
1 3
4 1
4 5
6 5
2 3 7
3 6 8
6 4 5

输出样例#1: 复制

7
7
8
5
5

题意:给你一棵树(双向边),树上2点肯定可以通过n-1边到达,现在要删除某些边,问你删除之后需要在从m条边选择一条最短的代替上,保证树上任意两点都可达。

思维:看到都用链剖、树剖写,表示不会,就用LCA瞎搞写一下。

如果连上当前这条边的话,那么这条边可以影响到U,V 2点之间一直到公共祖先(假设红线左边的是U,右边的的是V),想让V到达跟U一个层次高,然后往上蹦跶,一直用Min数组来维护更新。Min数组的作用就是来记录替代从U到U的2^j次方的祖先之间的任何一条边去掉花费的最小代价,还有V到公共祖先之间任何一条边去掉花费的最小代价。

维护Min就OK了。当你更新完之后还需要在调用倍增更新一遍最小值。因为有可能你寻找某一个等同的节点的时候回隔过去好多个点,例如图它会隔断,然后需要你去在调用倍增(从大区间到小区间)更新一遍最小值。同理也需要更新一下。必然对于     Min[x][j-1]=min(Min[x][j-1],Min[i][j]) 和 Min[anc[i][j-1]][j-1]=min(Min[anc[i][j-1]][j-1],Min[i][j]);

因为他是分成2段区间,都有可能需要大区间更新。

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
int head[maxn];
int cnt;
int d[maxn];//记录深度 
int fa[maxn];//父亲 
int anc[maxn][21];//anc数组记录第i个点的2^j的祖先是谁  倍增的思想 
int Min[maxn][21];//记录最小值 
int ans[maxn];
struct node{
    int t;
    int next;
    int id;
}no[maxn];
void add(int u,int v,int id)
{
    no[cnt]={v,head[u],id};
    head[u]=cnt++;
}
void dfs(int x)//dfs倍增求祖先 
{
    for(int i=1;i<=20;i++)//倍增 
        anc[x][i]=anc[anc[x][i-1]][i-1];
    for(int i=head[x];i!=-1;i=no[i].next)
    {
        int v=no[i].t;
        if(v!=anc[x][0])
        {
            anc[v][0]=x;//记录父亲节点 
            fa[v]=no[i].id;//记录那条边
            d[v]=d[x]+1;//更新深度
            dfs(v);
        }
    } 
} 
void Lca(int u,int v,int valu)
{
    if(d[u] < d[v])//一定深度最深的为标准 
        swap(u,v);
    for(int i=20;i>=0;i--)//上推找共同点 
    {
        if(d[anc[u][i]] >= d[v])//找到跟v一样高的或者比v低的点  不断更新u到v之间深度的点 
        {
            Min[u][i]=min(Min[u][i],valu);
            u=anc[u][i];
        }
    }
    if(u==v)//如果v,u在一棵树上 
        return ;
    for(int i=20;i>=0;i--)
    {
        if(anc[u][i] != anc[v][i])//如果不是一个叉子上  还需要往上  找到公共祖先之前一直往上 
        {
            Min[u][i] = min(Min[u][i],valu);
            Min[v][i] = min(Min[v][i],valu);
            u=anc[u][i];
            v=anc[v][i]; 
        }
    }
    Min[u][0] = min(Min[u][0],valu);//无论是否相等都需要更新u v两点 
    Min[v][0] = min(Min[v][0],valu);
} 
int main()
{
    memset(head,-1,sizeof(head));
    memset(Min,INF,sizeof(Min)); 
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v,i);//id标记边号   
        add(v,u,i);	
    }	
    d[1]=1;//深度 
    dfs(1);//倍增 
    for(int i=0;i<m;i++)
    {
        int u,v,Valu;
        scanf("%d%d%d",&u,&v,&Valu);
        Lca(u,v,Valu);//更新min 
    } 
    for(int j=20;j>0;j--)
    {
        for(int i=1;i<=n;i++)
        {
            Min[i][j-1]=min(Min[i][j-1],Min[i][j]);//大区间更新小区间 
            Min[anc[i][j-1]][j-1]=min(Min[anc[i][j-1]][j-1],Min[i][j]);//同上
        }
    }
    for(int i=2;i<=n;i++)//根节点是1  所以从2开始 
        ans[fa[i]]=Min[i][0];
    for(int i=1;i<n;i++)//边是第一个 
    {
        if(ans[i]==INF)
            ans[i]=-1;
        printf("%d\n",ans[i]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/passer__/article/details/81380540