CodeForces 827D Best Edge Weight (倍增 启发式合并 链剖 并查集)

6 篇文章 0 订阅
5 篇文章 0 订阅

D. Best Edge Weight

time limit per test2 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
You are given a connected weighted graph with n vertices and m edges. The graph doesn’t contain loops nor multiple edges. Consider some edge with id i. Let’s determine for this edge the maximum integer weight we can give to it so that it is contained in all minimum spanning trees of the graph if we don’t change the other weights.

You are to determine this maximum weight described above for each edge. You should calculate the answer for each edge independently, it means there can’t be two edges with changed weights at the same time.

Input
The first line contains two integers n and m (2 ≤ n ≤ 2·105, n - 1 ≤ m ≤ 2·105), where n and m are the number of vertices and the number of edges in the graph, respectively.

Each of the next m lines contains three integers u, v and c (1 ≤ v, u ≤ n, v ≠ u, 1 ≤ c ≤ 109) meaning that there is an edge between vertices u and v with weight c.

Output
Print the answer for each edge in the order the edges are given in the input. If an edge is contained in every minimum spanning tree with any weight, print -1 as the answer.

Examples
input
4 4
1 2 2
2 3 2
3 4 2
4 1 3
output
2 2 2 1
input
4 3
1 2 2
2 3 2
3 4 2
output
-1 -1 -1

中文题目:
这里写图片描述

思路:
这里写图片描述

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;

const int N = 300010;
const int P = 19;

int head[N],idc=0;
int u[N],v[N],C[N],n,m,od[N],fa[N];
int from[N];
bool in[N];

inline const int read(){
    register int x = 0;
    register char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9') x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
    return x;
}

struct Edge{
    int to, nxt, w, id;
}ed[N];

inline bool cmp(int aa, int bb){
    return C[aa] < C[bb];
}

void adde(int p, int q, int r, int idd){
    ed[++idc].to=q;
    ed[idc].nxt=head[p];
    head[p]=idc;
    ed[idc].w=r;
    ed[idc].id=idd;
}

int getfa(int x){
    if(fa[x] != x) fa[x] = getfa(fa[x]);
    return fa[x];
}

void unionn(int aa, int bb){
    aa = getfa(aa), bb = getfa(bb);
    fa[aa] = bb;
}

int dep[N],anc[N][20],mval[N][20],ans[N];
bool vis[N];
vector<int> G[N];
multiset<int> H[N];
multiset<int> *RL[N];

//priority_queue<int,vector<int>,greater<int> >q[N], del[N];//小根堆 
/*
int top(int x){  
    while(del[x].size() && del[x].top()==q[x].top()){
        del[x].pop(); q[x].pop();//先删除 
    }
    if( q[x].size() ) return q[x].top(); 
    else return -1; 
}  */

void dfs0(int s){
    vis[s] = true;
    for(int i=head[s]; i; i=ed[i].nxt){
        int v = ed[i].to;
        if(!vis[v]){
            dep[v] = dep[s] + 1;
            anc[v][0] = s;
            from[v] = ed[i].id;
            mval[v][0] = ed[i].w;
            dfs0( v );
        }
    }
}

pair<int,int> getMax(int l, int r){
    int maxx = -0x3f3f3f3f;
    if(dep[l] < dep[r]) swap(l, r);
    int dt = dep[l] - dep[r];
    for(int i=P; i>=0; i--)
        if(dt & (1<<i)){
            maxx = max(maxx, mval[l][i]);
            l = anc[l][i];
        }
    if(l == r) return make_pair(l, maxx);
    for(int i=P; i>=0; i--)
        if(anc[l][i] != anc[r][i]){
            maxx = max(maxx, mval[l][i]);
            maxx = max(maxx, mval[r][i]);
            l = anc[l][i];
            r = anc[r][i];
        }
    maxx = max(maxx, mval[l][0]);
    maxx = max(maxx, mval[r][0]);
    return make_pair(anc[l][0], maxx);
}

void solve(int s){
    vis[s] = true;
    RL[s] = &H[s];
    for(int i=head[s]; i; i=ed[i].nxt){
        int v = ed[i].to;
        if( !vis[v] ){
            solve( v );
            multiset<int> *ori, *add;
            if(RL[v]->size() > RL[s]->size())
                ori=RL[v], add=RL[s];
            else ori=RL[s], add=RL[v];
            for(multiset<int>::iterator it=add->begin(); it!=add->end(); ++it)
                ori->insert(*it);
            RL[s] = ori;
            /*top(v);
            while(q[v].size()) {
                q[s].push( q[v].top() ); q[v].pop(); 
                if( del[v].size() ){
                    del[s].push( del[v].top() ); 
                    del[v].pop(); 
                }
            }*/
        }
    }
    for(int i=0; i<G[s].size(); ++i){
        int cur = G[s][i];
        if(cur > 0) RL[s]->insert(cur);
        else RL[s]->erase(RL[s]->find(-cur));
        //if(cur > 0) q[s].push(cur);
        //else del[s].push(-cur);
    }
    //ans[from[s]] = top( s );
    //if( ans[from[s]] != -1 ) ans[from[s]]--;
    if(!RL[s]->size()) ans[from[s]] = -1;
    else ans[from[s]] = *(RL[s]->begin()) - 1;
}

int main(){
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for(register int i=1; i<=m; ++i){
        od[i] = i;
        u[i]=read();v[i]=read();C[i]=read();
    //  scanf("%d%d%d", &u[i], &v[i], &C[i]);
    }
    for(register int i=1; i<=n; ++i) fa[i] = i;
    sort(od+1, od+1+m, cmp);
    for(register int i=1; i<=m; ++i){//Kruskal
        int cur = od[i];
        if(getfa(u[cur]) != getfa(v[cur])){
            unionn(u[cur], v[cur]);
            in[cur] = true;
            adde(u[cur], v[cur], C[cur], cur);
            adde(v[cur], u[cur], C[cur], cur);
        }
    }
    dep[1] = 1;
    dfs0( 1 );//预处理ST 
    for(int i=1; i<=P; ++i)
        for(register int j=1; j<=n; ++j){
            anc[j][i] = anc[anc[j][i-1]][i-1];
            mval[j][i] = max(mval[j][i-1], mval[anc[j][i-1]][i-1]);
        }
    for(register int i=1; i<=m; ++i)
        if(!in[i]){//不在最小生成树上 
            pair<int,int> T = getMax(u[i], v[i]);//倍增求树外的边的ans
            ans[i] = T.second;
            if(ans[i] == -0x3f3f3f3f) ans[i] = -1;
            else ans[i]--;
            G[u[i]].push_back( C[i] );//标记方便之后的合并 
            G[v[i]].push_back( C[i] );
            G[T.first].push_back( -C[i] );
            G[T.first].push_back( -C[i] );
        }
    memset(vis, 0, sizeof(vis));
    solve( 1 );
    for(int i=1; i<=m; ++i) printf("%d ", ans[i]);//printf("%d\n", ans[i]);
    puts("");
    return 0;
}

隐掉的部分是最开始写的双堆,功能和multiset是一样的,但是好像当前我用的编译器不支持开这么多的堆,所以只好改成multiset。
上面是启发式合并的代码,下面来链剖。
线段树维护dfs序,区间min。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#define inf 0x7fffffff
#define N 400005
using namespace std;
int n,m,val[N],id[N],rk[N];
struct Edge{
    int v,next,w,id;
};
Edge e[2*N];
struct date{
    int u,v,w,id;
};
date aa[N];
int ans[N],fat[N],flag[N],head[N],num;
int find(int x){
    return fat[x]==x?x:fat[x]=find(fat[x]);
}
void init(){
    for(int i=1;i<=n;i++)
    fat[i]=i;
}
bool cmp(const date&a,const date&b){
    return a.w<b.w;
}
bool cmp_id(const date&a,const date&b){
    return a.id<b.id;
}
void adde(int i,int j,int w,int id){
    e[++num].v=j;
    e[num].next=head[i];
    e[num].w=w;
    e[num].id=id;
    head[i]=num;
}
void Krus(){
    int cnt=0;
    init();sort(aa+1,aa+m+1,cmp);
    for(int i=1;i<=m;i++){
        int u=aa[i].u,v=aa[i].v,w=aa[i].w;
        int p=find(u),q=find(v);
        if(p==q)continue;
        fat[p]=q;
        adde(u,v,w,aa[i].id);
        adde(v,u,w,aa[i].id);
        flag[aa[i].id]=1;
        cnt++;
        if(cnt==n-1)break;
    }
}
int dep[N],fa[N],top[N],son[N],idc,in[N],siz[N];
void dfs(int u){
    siz[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].v;
        if(v==fa[u])continue;
        fa[v]=u;
        dep[v]=dep[u]+1;
        val[v]=e[i].w;
        id[v]=e[i].id;
        dfs(v);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v])son[u]=v;
    }
}
void dfs(int u,int tp){
    in[u]=++idc;
    rk[idc]=u;
    top[u]=tp;
    if(son[u])dfs(son[u],tp);
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].v;
        if(v==son[u]||v==fa[u])continue;
        dfs(v,v);
    }
}
struct Node{
    int vmax,id,lz;
    Node *ls,*rs;
    Node(){
        lz=inf;
    }
    void update(){
        vmax=max(ls->vmax,rs->vmax);
    }
    void pushdown(int lf,int rg){
        if(lz!=inf){
            ls->lz=min(ls->lz,lz);
            rs->lz=min(rs->lz,lz);
            lz=inf;
        }
    }
};
Node *root,pool[N*2],*tail=pool;
Node *build(int lf,int rg){
    Node *nd=++tail;
    if(lf==rg){
        nd->id=id[rk[lf]];
        nd->vmax=val[rk[lf]];
    }
    else{
        int mid=(lf+rg)>>1;
        nd->ls=build(lf,mid);
        nd->rs=build(mid+1,rg);
        nd->update();
    }
    return nd;
}
int query_seg(Node *nd,int lf,int rg,int L,int R){
    if(L<=lf&&rg<=R){
        return nd->vmax;
    }
    int mid=(lf+rg)>>1,rt=0;
    if(L<=mid)
        rt=max(rt,query_seg(nd->ls,lf,mid,L,R));
    if(R>mid)
        rt=max(rt,query_seg(nd->rs,mid+1,rg,L,R));
    return rt;
}
void modify(Node *nd,int lf,int rg,int L,int R,int val){
    if(L<=lf&&rg<=R){
        nd->lz=min(nd->lz,val);
        return;
    }
    int mid=(lf+rg)>>1;
    if(L<=mid)
        modify(nd->ls,lf,mid,L,R,val);
    if(R>mid)
        modify(nd->rs,mid+1,rg,L,R,val);
}
int query(int u,int v){
    int rt=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        rt=max(rt,query_seg(root,1,n,in[top[u]],in[u]));
        u=fa[top[u]];
    }
    if(u==v)return rt;
    if(dep[u]<dep[v])swap(u,v);
    rt=max(rt,query_seg(root,1,n,in[son[v]],in[u]));
    return rt;
}
void modify(int u,int v,int w){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        modify(root,1,n,in[top[u]],in[u],w);
        u=fa[top[u]];
    }
    if(u==v)return;
    if(dep[u]<dep[v])swap(u,v);
    modify(root,1,n,in[son[v]],in[u],w);
}
void get_ans(Node *nd,int lf,int rg){
    if(lf==rg){
        ans[nd->id]=nd->lz;
        return;
    }
    nd->pushdown(lf,rg);
    int mid=(lf+rg)>>1;
    get_ans(nd->ls,lf,mid);
    get_ans(nd->rs,mid+1,rg);
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&aa[i].u,&aa[i].v,&aa[i].w),aa[i].id=i;
    Krus();

    sort(aa+1,aa+m+1,cmp_id);
    fa[1]=0;dep[1]=1;
    dfs(1);
    dfs(1,1);
    root=build(1,n);
    for(int i=1;i<=m;i++){
        if(flag[i])continue;
        ans[i]=query(aa[i].u,aa[i].v)-1;
    //  printf("%d %d %d %d\n",i,aa[i].u,aa[i].v,ans[i]);
        modify(aa[i].u,aa[i].v,aa[i].w-1);
    }
    get_ans(root,1,n);
    for(int i=1;i<=m;i++)
    printf(ans[i]==inf?"-1\n":"%d\n",ans[i]);
}

考虑第一种情况,我们是枚举所有不在最小生成树上的边,而第二种情况,本来是枚举所有在最小生成树上的边,转化之后也变成了枚举所有不在最小生成树上的边了,好像可以一起搞定
考虑第一次查询的范围,是枚举的边左右两点在最小生成树上的一条链,而第二次更新的范围也是左右两点的一条链
范围的相同更加印证了两种情况可以一起来讨论了
现在对于每次更新的范围和查询范围相同,依然可以用树链剖分来搞定

现在考虑,查询自不用说,复杂度非常优美,考虑更新,有没有比起树链剖分更好写,但是同样高效的方法
查询是用的倍增,显然更新没办法像倍增一样跳着跳着地更新,所以必然是其他方法
考虑每次更新是取的min值,就是说,如果一条链已经被一条边更新了,再被另一条边权较大的边更新,是没有用的。这就提醒我们,已经被边权小的边更新过答案的区域,是不会再被更大的边更新的,在考虑每次更新是独立计算答案,最后需要的只是所有不在最小生成树上的边更新后叠加的值,对于更新的顺序完全不关心
那么我们就可以自定义更新顺序了,选择按照边权排序来更新就行
这样之前更新的区域就不用重复更新了,可以用类似并查集或者链表的方法跳着跳着更新还没有更新的区域

这样由于每个点只会被更新一次,复杂度就是均摊O(m)的了
不仅非常优秀而且意外好写

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define LL long long
#define N 100010
#define P 18
using namespace std;

inline int read(){
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

const int INF = 0x3f3f3f3f;

int n, m, opt, idc = 0, cnt = 0;
int head[N], acc[N][P+1], maxx[N][P+1];
int dep[N], ans[N], to[N], fa[N], in[N];

struct Edge{
    int to, from, nxt, w, id;
}ed[N << 1], tree[N << 1];

inline void adde(int u, int v, int w, int id){
    tree[++idc].to = v;
    tree[idc].nxt = head[u];
    tree[idc].w = w;
    tree[idc].id = id;
    head[u] = idc;
}

inline int getfa(int x){
    if(x == fa[x]) return x;
    return fa[x] = getfa( fa[x] );
}

inline bool cmp(Edge a, Edge b){ return a.w < b.w;}

void kruscal(){
    sort(ed+1, ed+m+1, cmp);
    for(register int i=1; i<=m && cnt<n-1; i++){
        int x = getfa( ed[i].from ), y = getfa( ed[i].to );
        if(x != y){
            ++cnt; in[i] = 1; fa[x] = y;
            adde(ed[i].from, ed[i].to, ed[i].w, ed[i].id);
            adde(ed[i].to, ed[i].from, ed[i].w, ed[i].id);
        }
    }
}

inline int getout(int u, int v, int pos){
    ans[pos] = 0;
    if(dep[u] < dep[v]) swap(u, v);
    for(int i=P; i>=0; i--)
        if(dep[acc[u][i]] >= dep[v]){
            ans[pos] = max(ans[pos], maxx[u][i]);
            u = acc[u][i];
        }
    if(u == v) {
        ans[pos]--; return u;
    }
    for(int i=P; i>=0; i--)
        if(acc[u][i] != acc[v][i]){
            ans[pos] = max(ans[pos], maxx[u][i]);
            ans[pos] = max(ans[pos], maxx[v][i]);
            u = acc[u][i]; v = acc[v][i];
        }
    ans[pos] = max(ans[pos], maxx[u][0]);
    ans[pos] = max(ans[pos], maxx[v][0]);
    ans[pos]--;
    return acc[u][0];
}

inline void dfs(int u, int f){
    acc[u][0] = f;
    for(int i=1; i<=P; i++){
        acc[u][i] = acc[acc[u][i-1]][i-1];
        maxx[u][i] = max(maxx[u][i-1], maxx[acc[u][i-1]][i-1]);
    }
    for(int i=head[u]; i; i=tree[i].nxt){
        int v = tree[i].to;
        if(v == f) continue;
        dep[v] = dep[u] + 1;
        to[v] = tree[i].id;
        maxx[v][0] = tree[i].w;
        dfs(v, u);
    }
}

inline void getin(int u, int v, int val){
    v = getfa(v);
    while (dep[u] < dep[v]){
        ans[to[v]] = min(ans[to[v]], val);
        int ff = getfa( acc[v][0] );
        fa[v] = ff;
        v = getfa(v);
    }
}

int main(){
    freopen ("weight.in", "r", stdin);
    freopen ("weight.out", "w", stdout);
    scanf("%d%d%d", &n, &m, &opt);
    for(register int i=1; i<=m; i++){
        ed[i].from = read(), ed[i].to = read(), ed[i].w = read();
        ed[i].id = i;
    }
    for(register int i=1; i<=n; i++) fa[i] = i;
    kruscal(); dfs(1, 1);
    for(register int i=1; i<=n; i++) fa[i] = i;
    memset(ans, INF, sizeof(ans));
    for(register int i=1; i<=m; i++){
        if( in[i] ) continue;
        int u = ed[i].from, v = ed[i].to;
        int LCA = getout(ed[i].from, ed[i].to, ed[i].id);
        getin(LCA, u, ed[i].w-1);
        getin(LCA, v, ed[i].w-1);
    }
    for(register int i=1; i<=m; i++)
        if(ans[i] == INF) printf("-1 ");
        else printf("%d ", ans[i]);
    return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值