[校内模拟]最小生成树(Tarjan)

=== ===

[校内模拟]最小生成树(Tarjan)_第1张图片
[校内模拟]最小生成树(Tarjan)_第2张图片
[校内模拟]最小生成树(Tarjan)_第3张图片

=== ===

题解

ATP当时考试考这题的时候就写了个暴力还写挂了。。。

首先联想Kruskal的操作过程可以想到,对于一条权值为w的边(u,v),如果权值小于它的边已经联通了u和v,那么这条边就一定不会被加到最小生成树里面;如果再加上除了(u,v)以外其它权值等于w的边就能把u和v联通,那么这条边就可能出现在某棵最小生成树里面;否则就说明想要把u和v联通,这条边是必须的,也就是它一定出现在所有最小生成树里面。

none的情况非常好判,重点就是判后两种情况。对于at least one的情况来说肯定不能每次枚举某条边加不加,就要一下全判出来。可以发现那些any的边,它是小于等于w的所有边里面联通(u,v)的必须边,去掉这条边,图就会不连通。

有没有想到什么啊对吧= =没错这就是无向图中的桥边!每次把和w权值相等的边加入进去,并且需要注意的是因为前面处理完小于w的边了以后图中已经有了一些连通块,所以加边的时候不是直接连接u和v,而是连接find(u)和find(v),即u和v在集合中的代表元素。那么显然如果枚举到某条边的时候它的两个端点已经在同一个连通块中了,说明小于它的边就可以联通它两个端点,就判成none就可以了。然后构造出这个图以后里面所有的桥边就是any,剩下的就是at least one啦。

注意的问题是每次都要重新构图,清数组的时候需要注意。不能用memset否则会T飞的啦。用一个点清一个点就可以了。

代码

#include
#include
#include
using namespace std;
int n,m,tot,cnt,dfc,father[100010],w[100010],low[100010],last[100010],p[100010];
int next[200010],a[200010],ans[100010],rec[100010],id[100010];
bool vis[100010];
struct edge{
    int x,y,w,id;
}e[100010];
int comp(edge a,edge b){return a.wvoid add(int x,int y,int i){
    tot++;a[tot]=y;next[tot]=p[x];id[tot]=i;p[x]=tot;
}
int find(int x){
    if (father[x]==x) return father[x];
    father[x]=find(father[x]);
    return father[x];
}
bool same(int i,int j){
    if (i%2==0) return i-1==j;
    else return i+1==j;
}
void tarjan(int u){
    w[u]=low[u]=++dfc;vis[u]=true;
    for (int i=p[u];i!=0;i=next[i])
      if (vis[a[i]]==false){
          last[a[i]]=i;
          tarjan(a[i]);
          low[u]=min(low[u],low[a[i]]);
      }else if (!same(last[u],i)) low[u]=min(low[u],w[a[i]]);
}//注意在判是不是同一条边的时候,无向边加的正反边要算同一条
void check(){
    for (int i=1;i<=cnt;i++)
      if (w[rec[i]]==0)
        tarjan(rec[i]);
    for (int i=1;i<=cnt;i++){
        int u=rec[i];
        if (low[u]==w[u])
          ans[id[last[u]]]=2;
    }
    for (int i=1;i<=cnt;i++)
      for (int j=p[rec[i]];j!=0;j=next[j])
        if (ans[id[j]]==0) ans[id[j]]=1;
}
int main()
{
    freopen("mst.in","r",stdin);
    freopen("mst.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
        e[i].id=i;
    }
    sort(e+1,e+m+1,comp);
    memset(w,-1,sizeof(w));
    for (int i=1;i<=n;i++) father[i]=i;
    for (int i=1;i<=m;){
        int tail=i;tot=cnt=dfc=0;
        while (e[tail].w==e[i].w){
            int r1,r2;
            r1=find(e[tail].x);r2=find(e[tail].y);
            if (r1!=r2){            
                if (w[r1]!=0){//注意rec和p都只能初始化一次,尤其是p
                    rec[++cnt]=r1;p[r1]=0;
                }
                if (w[r2]!=0){
                    rec[++cnt]=r2;p[r2]=0;
                }
                w[r1]=w[r2]=0;
                low[r1]=low[r2]=last[r1]=last[r2]=0;
                vis[r1]=vis[r2]=false;
                add(r1,r2,e[tail].id);add(r2,r1,e[tail].id);
            }//加边
            tail++;
        }
        check();
        for (int j=i;jint r1,r2;//把处理完的边加到并查集里
            r1=find(e[j].x);r2=find(e[j].y);
            if (r1!=r2) father[r2]=r1;
        }
        i=tail;
    }
    for (int i=1;i<=m;i++)
      if (ans[i]==0) printf("none\n");
      else if (ans[i]==1) printf("at least one\n");
      else printf("any\n");
    return 0;
}

你可能感兴趣的:(BZOJ,杂七杂八的图论)