Codeforces 827D Best Edge Weight (最小生成树 + 树链剖分/倍增/并查集)

Codeforces 827D Best Edge Weight

原题地址http://codeforces.com/problemset/problem/827/D

题意:
给你N个点M条边的带边权无向联通图,现在对于每条边,问这条边的权值最大可以是多
少,使得这条边在该无向图的所有最最小成树中?

数据范围
2 ≤N ≤ 2*10 5 5 , N - 1 ≤ M ≤ 2*10 5 5

题解:
首先我们跑出这个图的最小生成树。

那么:
对于每条树边,它必须要比所有可以代替它的非树边要小(最小值-1),
可以代替它的非树边的两端点必须横跨这条树边。

对于每条非树边,它必须比最大的它可以代替的树边要小(最大值-1)。
它可以代替的树边就是它的两个端点在树上对应的一条链上的边。

非树边的情况,链上最大值比较好求,倍增即可,
但是非树边呢,对于每条树边去找可以代替它的非树边的最小值显然不太好操作,
(其实我最开始想的是拿一个set维护,然后像天天爱跑步那样每到一个子树删一些非树边再加一些非树边)

但是可以转换思路,不对于每条树边去找可以代替它的非树边,
而是对于每条非树边,去更新它可以更新到的树边。(常见转化

就变成了:
求树链上的最大值,更改一条链上的值。

sol1. 树链剖分: O(mlogn^2)
这个很容易用链剖加个线段树维护。
(一种是区间打标记,初始flag=inf,最后查询一个点时就是这样一层层下来的最小标记的值,另一种就每次去修改每个点,只要一个区间的max大于更新值就去修改,因为如果从小到大枚举非树边,每个树边实则只会被更新一次,均摊依旧 nlogn^2。
(考场上觉得代码量大不敢写。其实注意一点就不会错。要小心争取一次写对。)

UPD:关于树链剖分的注意事项:
因为是边权化点权,所以最后的lca不能modify。
对于这一点,实际上因为最后都跳到一条重链上,之前已记录了重儿子,就修改[son[v],u]即可(dep[v]< dep[u])。
但是需要注意的是,假设u,v最后跳到了一个点,就不存在区间,dfn[son[v]]>dfn[u],需要在线段树修改时判断L,R的大小关系,以便及时return -inf。(所以在modify时需要注意两端点的dfn谁大谁小。)

树链剖分代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=100055;
const int M=100055; //点权化边权
const int inf=0x3f3f3f3f;
vector<int> NT,to[N],W[N];
struct edge
{
    int u,v,w,id;
}E[M];
bool cmp(const edge &A,const edge &B)
{
    return A.w<B.w;
}
bool cmpid(const edge &A,const edge &B)
{
    return A.id<B.id;
}
int n,m,xx;
int fa[N],son[N],seq[N],dfn[N],pl[N],size[N],dep[N],top[N],inc=0,w[N],ans[N];
struct node
{
    int ls,rs;
    int mx,flag; //flag树边更新值  mx原树边值max  
}tr[2*N];
int root,tail=0;
int getfa(int x)
{
    if(pl[x]==x)return pl[x];
    else return pl[x]=getfa(pl[x]);
}
void dfs(int u,int f)
{
    dep[u]=dep[f]+1;
    fa[u]=f;
    size[u]=1;
    int sz=to[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=to[u][i];
        if(v==f) continue;
        w[v]=W[u][i];
        dfs(v,u); 
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}
void gettp(int u,int f,int tp)
{
    top[u]=tp;
    inc++; seq[inc]=u; dfn[u]=inc;
    if(son[u]) gettp(son[u],u,tp);
    int sz=to[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=to[u][i];
        if(v==f||v==son[u]) continue;
        gettp(v,u,v); 
    }
}
void update(int nd)
{
    int ls=tr[nd].ls; int rs=tr[nd].rs;
    tr[nd].mx=max(tr[ls].mx,tr[rs].mx); 
}
void build(int &nd,int lf,int rg)
{
    nd=++tail; 
    tr[nd].flag=inf;
    if(lf==rg)
    {
        tr[nd].mx=E[w[seq[lf]]].w;
        return;
    }
    int mid=(lf+rg)>>1;
    build(tr[nd].ls,lf,mid);
    build(tr[nd].rs,mid+1,rg);
    update(nd);
}
int modify(int nd,int lf,int rg,int L,int R,int val)
{
    if(lf>rg) return -inf;
    if(L<=lf&&rg<=R)
    {
        tr[nd].flag=min(val,tr[nd].flag);
        return tr[nd].mx;
    }
    int mid=(lf+rg)>>1;
    int ret=-inf;
    if(L<=mid)  ret=max(ret,modify(tr[nd].ls,lf,mid,L,R,val));
    if(R>mid) ret=max(ret,modify(tr[nd].rs,mid+1,rg,L,R,val));
    return ret;
}
int modify(int u,int v,int ww)  ///修改 u,v,w 
{
    int ret=-inf;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ret=max(ret,modify(root,1,n,dfn[top[u]],dfn[u],ww)); u=fa[top[u]]; 
    }
    if(dep[u]<dep[v]) swap(u,v);
    ret=max(ret,modify(root,1,n,dfn[son[v]],dfn[u],ww));
    return ret;
}
int query(int nd,int lf,int rg,int pos)
{
    if(lf==rg) return tr[nd].flag;
    int mid=(lf+rg)>>1;
    int ret=tr[nd].flag;
    if(pos<=mid)
    ret=min(ret,query(tr[nd].ls,lf,mid,pos));
    else 
    ret=min(ret,query(tr[nd].rs,mid+1,rg,pos));
    return ret;
}
void getans(int u,int f)
{
    int sz=to[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=to[u][i];
        if(v==f) continue;
        ans[w[v]]=query(root,1,n,dfn[v])-1;
        getans(v,u); 
    }
}
int main() 
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        E[i].id=i;
    }   
    sort(E+1,E+m+1,cmp);
    for(int i=1;i<=n;i++) pl[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u=E[i].u; int v=E[i].v;
        int fx=getfa(u); int fy=getfa(v);
        if(fx!=fy) 
        {
            pl[fx]=fy;
            to[u].push_back(v); to[v].push_back(u);
            W[u].push_back(E[i].id); W[v].push_back(E[i].id);
        }
        else 
        {
            NT.push_back(E[i].id);
        }
    }
    sort(E+1,E+m+1,cmpid);
    dfs(1,1);
    gettp(1,1,1);
    build(root,1,n);
    int sz=NT.size();
    for(int i=0;i<sz;i++)
    {
        int tmp=NT[i]; 
        int u=E[tmp].u; int v=E[tmp].v;
        ans[E[tmp].id]=modify(u,v,E[tmp].w)-1;
    }
    getans(1,1);
    for(int i=1;i<=m;i++)
    if(ans[i]>=inf-1) printf("-1 ");else printf("%d ",ans[i]);  
    return 0;
}

但是好长啊。很容易出点错误。
于是,
sol2. 倍增: O(mlogn+nlogn)
如果对于非树边,可以倍增求链上最大值,难道不能倍增地修改链上的值吗?
既然暴力是一个一个地跳,修改,那么思考如何一块一块地跳,修改。

可以用与这道题类似的思想。

倍增跳lca的时候一起更新ans[u][i](表示u点向上2^i条边的最大答案)的答案
最后从anc[u][P]开始逐层下拆取min:

    for(int i=P;i>=1;i--)
    for(int u=1;u<=n;u++)
    {
        ans[u][i-1]=min(ans[u][i],ans[u][i-1]);
        ans[anc[u][i-1]][i-1]=min(ans[u][i],ans[anc[u][i-1]][i-1]);
    }

答案就在ans[u][0]里。

倍增代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=200005;
const int P=17;
const int inf=0x3f3f3f3f;
int n,m,anc[N][P+3],w[N][P+3],ans[N][P+3],answer[N],fa[N],dep[N],e[N];  //w存储树边边权最大值,ans存被非树边更新的最小值答案 
vector<int> to[N],NT,W[N];
struct egde
{
    int u,v,w,id;
}E[N];
bool cmpw(const egde &A,const egde &B)
{return A.w<B.w;}
bool cmpid(const egde &A,const egde &B)
{return A.id<B.id;}
int getfa(int x)
{
    if(fa[x]==x) return x;
    else return fa[x]=getfa(fa[x]);
}
void dfs(int u,int f)
{
    dep[u]=dep[f]+1;
    anc[u][0]=f;
    for(int i=1;i<P;i++)
    {
        anc[u][i]=anc[anc[u][i-1]][i-1];
        w[u][i]=max(w[u][i-1],w[anc[u][i-1]][i-1]);
    }
    int sz=to[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=to[u][i];
        if(v==f) continue;
        e[v]=W[u][i];
        w[v][0]=E[W[u][i]].w;
        dfs(v,u);
    }   
}
int modify(int u,int v,int val)
{
    if(dep[u]<dep[v]) swap(u,v);
    int d=dep[u]-dep[v];
    int ret=-inf;
    for(int i=0;d;d>>=1,i++)
    if(d&1) {ret=max(ret,w[u][i]); ans[u][i]=min(ans[u][i],val); u=anc[u][i]; }
    if(u==v) return ret;
    for(int i=P-1;i>=0;i--)
    {
        if(anc[u][i]!=anc[v][i])
        {
            ret=max(ret,w[u][i]); ret=max(ret,w[v][i]);
            ans[u][i]=min(ans[u][i],val); ans[v][i]=min(ans[v][i],val); 
            u=anc[u][i]; v=anc[v][i];
        }
    }
    ret=max(ret,w[u][0]); ret=max(ret,w[v][0]);
    ans[u][0]=min(ans[u][0],val); ans[v][0]=min(ans[v][0],val); 
    u=anc[u][0]; v=anc[v][0];
    return ret; 
}
void pushdown()
{
    for(int i=P;i>=1;i--)
    for(int u=1;u<=n;u++)
    {
        ans[u][i-1]=min(ans[u][i],ans[u][i-1]);
        ans[anc[u][i-1]][i-1]=min(ans[u][i],ans[anc[u][i-1]][i-1]);
    }
    for(int u=1;u<=n;u++)
    answer[e[u]]=ans[u][0]-1;   
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        E[i].id=i;
    }
    sort(E+1,E+m+1,cmpw);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u=E[i].u; int v=E[i].v;
        int fx=getfa(u);  int fy=getfa(v);
        if(fx!=fy)
        {
            fa[fx]=fy;
            to[u].push_back(v); to[v].push_back(u);
            W[u].push_back(E[i].id); W[v].push_back(E[i].id);           
        }
        else NT.push_back(E[i].id);
    }
    sort(E+1,E+m+1,cmpid);
    memset(ans,0x3f,sizeof(ans));
    dfs(1,1);
    int sz=NT.size();
    for(int i=0;i<sz;i++)
    {
        int tmp=NT[i];
        answer[tmp]=modify(E[tmp].u,E[tmp].v,E[tmp].w)-1;
    }
    pushdown();
    for(int i=1;i<=m;i++)
    if(answer[i]>=inf-1) printf("-1 "); else printf("%d ",answer[i]);
    return 0;
}

sol3. 并查集: O(nlogn)

上述两种方式思路类似,都是逐个去更新,
但我们还有个没有用的性质:
如果从小到大枚举非树边,每个树边实则只会被更新一次,它只会被第一个更新到它的非树边更新。
暴力的复杂度就多在,不需要走的点又走了一边。
实际上如果我们把走过的边就缩掉,那么每个树边只会被走一次。
这个可以用并查集或链表维护。
这个是O(n)的,倍增求lca是logn的,最终是O(nlogn)。

并查集代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=200005;
const int P=17;
const int inf=0x3f3f3f3f;
int n,m,anc[N][P+3],fa[N],dep[N],e[N],ans[N],w[N][P]; 
bool vis[N];
vector<int> to[N],NT,W[N];
struct egde
{
    int u,v,w,id;
}E[N];
bool cmpw(const egde &A,const egde &B)
{return A.w<B.w;}
bool cmpid(const egde &A,const egde &B)
{return A.id<B.id;}
int getfa(int x)
{
    if(fa[x]==x) return x;
    else return fa[x]=getfa(fa[x]);
}
void dfs(int u,int f)
{
    anc[u][0]=f;
    dep[u]=dep[f]+1;
    for(int i=1;i<P;i++)
    {
        anc[u][i]=anc[anc[u][i-1]][i-1];
        w[u][i]=max(w[u][i-1],w[anc[u][i-1]][i-1]);
    }
    int sz=to[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=to[u][i];
        if(v==f) continue;
        e[v]=W[u][i];
        w[v][0]=E[W[u][i]].w;
        dfs(v,u);
    }
}
int query(int u,int v,int &lca)
{
    if(dep[u]<dep[v]) swap(u,v);
    int ret=-inf;
    int d=dep[u]-dep[v];
    for(int i=0;d;d>>=1,i++)
    if(d&1){ret=max(ret,w[u][i]); u=anc[u][i];}
    if(u==v) {lca=u; return ret;}
    for(int i=P-1;i>=0;i--)
    {
        if(anc[u][i]!=anc[v][i])
        {
            ret=max(ret,w[u][i]); ret=max(ret,w[v][i]);
            u=anc[u][i]; v=anc[v][i];
        }
    }
    ret=max(ret,w[u][0]); ret=max(ret,w[v][0]);
    lca=anc[u][0];
    return ret;
}
void modify(int u,int v,int val)
{
    u=getfa(u);
    if(dep[u]<dep[v]) return;
    while(dep[u]>dep[v])
    {
        ans[e[u]]=val-1;
        int nxt=getfa(anc[u][0]);
        fa[getfa(u)]=nxt;
        u=nxt;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        E[i].id=i;
    }
    sort(E+1,E+m+1,cmpw);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u=E[i].u; int v=E[i].v;
        int fx=getfa(u);  int fy=getfa(v);
        if(fx!=fy)
        {
            fa[fx]=fy;
            to[u].push_back(v); to[v].push_back(u);
            W[u].push_back(E[i].id); W[v].push_back(E[i].id);           
        }
        else NT.push_back(E[i].id);
    }
    sort(E+1,E+m+1,cmpid);
    memset(w,0x3f,sizeof(w));
    memset(ans,0x3f,sizeof(ans));
    dfs(1,1);
    for(int i=1;i<=n;i++) fa[i]=i;
    int sz=NT.size();
    for(int i=0;i<sz;i++)
    {
        int tmp=NT[i];
        int lca;
        ans[tmp]=query(E[tmp].u,E[tmp].v,lca)-1;
        modify(E[tmp].u,lca,E[tmp].w);
        modify(E[tmp].v,lca,E[tmp].w);
    }
    for(int i=1;i<=m;i++)
    if(ans[i]>=inf-1) printf("-1 "); else printf("%d ",ans[i]);
    return 0;
}

于是,一道题收获了三种思路,可喜可贺。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值