codeforce 240E

/*

最小树形图+保存路径

第一次想错了,各种wa,tle后网上看资料,找到一篇错误的题解。。。

最后用对着正解分析了一波,感觉对最小树形图又有了新的理解:最小树形图的精髓在于每张图更新的时间信息!

第一次感觉到如此神奇的算法,解释分散在注释里了

*/

/*
cf240e
最小树形图:输出路径板子 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXM 2000005
#define ll long long 
#define INF 0x7fffffff
using namespace std;
int pre[MAXN],vis[MAXN],in[MAXN],id[MAXN];
int usedEdge[MAXN],preEdge[MAXN];
struct Edge{
    int u,v,w,ww,id;
    Edge(){}
    Edge(int uu,int vv,int _w,int _ww,int ii):u(uu),v(vv),w(_w),ww(_ww),id(ii){}
}edge[MAXM];
struct Used{
    int pre,id;
}cancle[MAXM];//保留每次更新图时被取消的边id 
int zhuliu(int root,int n,int m){
    memset(usedEdge,0,sizeof usedEdge);
    int total=m,res=0;
    int u,v,w;
    while(1){
        for(int i=0;i<n;i++)in[i]=INF;
        for(int i=0;i<m;i++){
            u=edge[i].u,v=edge[i].v,w=edge[i].w;
            if(u!=v && w<in[v]){
                in[v]=w;
                pre[v]=u;
                //记录这个顶点所在边的编号 !!!
                preEdge[v]=edge[i].id;
            }
        }
        for(int i=0;i<n;i++)
            if (i!=root && in[i]==INF) return -1;
        
        int tn=0;
        memset(id,-1,sizeof id);
        memset(vis,-1,sizeof vis);
        in[root]=0;
        for(int i=0;i<n;i++){
            res+=in[i];
            v=i;
            //这条边被加入到当前图集合E中,这个点所在的边使用次数+1 !!!!
            if(i!=root) usedEdge[preEdge[i]]++;
            while(v!=root && id[v]==-1 && vis[v]!=i){
                vis[v]=i;
                v=pre[v];
            }
            if(id[v]==-1 && v!=root){
                for(u=pre[v];u!=v;u=pre[u])
                    id[u]=tn;
                id[v]=tn++;
            }
        } 
        if(tn==0) break;
        for(int i=0;i<n;i++)
            if(id[i]==-1) id[i]=tn++;
        
        //准备更新旧图 
        for(int i=0;i<m;i++){
            u=edge[i].u,v=edge[i].v;
            edge[i].u=id[u],edge[i].v=id[v];
            if(id[u]!=id[v]){
                edge[i].w-=in[v];
                //将该边在旧图中的编号取消 
                cancle[total].id=edge[i].id;
                cancle[total].pre=preEdge[v]; 
                ///将这条边id重新编号
                edge[i].id=total++;             
            }
        }
        n=tn;
        root=id[root];
    }
    //统计新建立(被重新编号)的边 !!!因为每重新编号一条边,就说明有一条边被取消一次,一因此需要将那条变得增加量也取消 
    //为什么要倒着往回遍历?因为必须沿着最终形成图的状态回溯逐步回溯到原图,找到边原始的id  
    //肯定是新建立的边导致了之前的边被取消掉, 
    for(int i=total-1;i>=m;i--)
        if(usedEdge[i]){//如果这条边被使用过(如果一条边仅仅是被重新编号而未被使用过,即只参与了边权值的改变但没有真正加入到某一次循环图中去,那么和其相关的边不用被取消) 
            usedEdge[cancle[i].id]++;//
            usedEdge[cancle[i].pre]--;//把之前加的减掉 
        }
    return res;
} 

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i=0;i<m;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            u--;v--;
            edge[i]=Edge(u,v,w,w,i);
        }
        int root=0;
        int ans=zhuliu(root,n,m);
        if(ans==-1 || ans==0)
            printf("%d\n",ans); 
        else {
            printf("%d\n",ans);
            for(int i=0;i<m;i++)
                if(edge[i].ww==1 && usedEdge[i])
                    printf("%d ",i+1);
            printf("\n");
        }
    }    
    return 0;
} 

猜你喜欢

转载自www.cnblogs.com/zsben991126/p/9807903.html