补题记录- 2019暑期训练 东北赛 E. Minimum Spanning Tree

题目链接:https://codeforces.com/gym/247802/problem/E
这个链接可能进不去。

题面

E. Minimum Spanning Tree

time limit per test 2.0 s
memory limit per test 512 MB

In the mathematical discipline of graph theory, the line graph of a simple undirected weighted graph G is another simple undirected weighted graph L(G) that represents the adjacency between every two edges in G.

Precisely speaking, for an undirected weighted graph G without loops or multiple edges, its line graph L(G) is a graph such that:

Each vertex of L(G) represents an edge of G.
Two vertices of L(G) are adjacent if and only if their corresponding edges share a common endpoint in G, and the weight of such edge between this two vertices is the sum of their corresponding edges’ weight.

A minimum spanning tree(MST) or minimum weight spanning tree is a subset of the edges of a connected, edge-weighted undirected graph that connects all the vertices together, without any cycles and with the minimum possible total edge weight. That is, it is a spanning tree whose sum of edge weights is as small as possible.

Given a tree G, please write a program to find the minimum spanning tree of L(G).

Input
The first line of the input contains an integer T(1≤T≤1000), denoting the number of test cases.

In each test case, there is one integer n(2≤n≤100000) in the first line, denoting the number of vertices of G.

For the next n−1 lines, each line contains three integers u,v,w(1≤u,v≤n,u≠v,1≤w≤109), denoting a bidirectional edge between vertex u and v with weight w.

It is guaranteed that ∑n≤106.

Output
For each test case, print a single line containing an integer, denoting the sum of all the edges’ weight of MST(L(G)).

Example
input
2
4
1 2 1
2 3 2
3 4 3
4
1 2 1
1 3 1
1 4 1
output
8
4
题意就是给一颗树G,然后图L(G)定义为,把树的每条边作为节点,相邻的两条边(在图G中共享一个节点的边)之间有一条路径,路径的权值为这两条边的权值和。求图L(G)的最小生成树。

思路

1:较为暴力的思路
首先需要建图L(G),如果直接建图,然后找最小生成树,如果一个节点的度为(n-1),那么以这个节点的边作为节点,在L中建图,就会有(N^2)条边,再去找最小生成树会T。
但是可以发现建图的时候具有贪心策略,如果目前节点的度大于2,那么先找其中权值最小的边,把这条边就是节点之一,分别于剩下的边建立边即可。所以如果一个节点的度为n-1,那么由它建立的L(G)中的边最多有n-1条。所以建图时只需要讨论目前节点的度即可。
2:巧妙思路
会发现这样得到的最小生成树符合贪心策略,即对于每一个节点,如果度大于1,现对于每一个节点的边权从大到小排序,每一次找最小的边,然后与别的边建立联系,然后ans就加上这个权值与目前节点的度减一的乘机,然后对于这个节点其余的边,ans直接加上边权即可。具体怎么更新可以看代码。

代码

第1中方法

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define _fileout freopen("out.txt","w",stdout)
#define _filein freopen("in.txt","r",stdin)
#define ok(i) printf("ok%d\n",i)
typedef double db;
typedef long long ll;
typedef pair<ll,ll> PII;
typedef pair<pair<ll,ll>,ll> PIII;
const double PI = acos(-1.0);
const ll MOD=1e9+7;
const ll NEG=1e9+6;
const int MAXN=1e5+10ll;
const ll INF=10000000000000000ll;
const double eps=1e-9;
map<PII,ll> M;
vector<PII> G[MAXN];//存图
struct edge{
    ll u,v;
    ll w;
    bool operator < (edge a){
        return w<a.w;
    }
}E[MAXN*30LL];//存后来的L(G)的所有的边。
ll fa[MAXN];
ll get(ll x){return x==fa[x]?x:fa[x]=get(fa[x]);}//并查集
ll n;
void init()//每一次都要初始化
{
    for(int i=1;i<=n;i++)fa[i]=i;
    M.clear();
    memset(G,0,sizeof(G));
}
int main(void)
{
    ll _;
    scanf("%I64d",&_);
    while(_--)
    {
        scanf("%I64d",&n);
        init();
        for(ll i=1;i<n;i++)
        {
            ll x,y,z;
            scanf("%I64d%I64d%I64d",&x,&y,&z);
            G[x].pb(make_pair(y,z));
            G[y].pb(make_pair(x,z));
        }
        ll o=0;//记录L(G)图中的所有的点,即原图G中的边对应于L(G)的编号。
        ll num=0;//记录L(G)中的边的数量。
        for(ll i=1;i<=n;i++)
        {
            ll len=G[i].size();
            if(len<=1)continue;//如果目前节点只有一个边,那么它队友L(G)没有贡献。
            else if(len==2)//如果目前的节点只有两条边,那么把这两条边作为节点,权值之和作为边权,加入到L(G)中。
            {
                ll x1=i;
                ll x2=G[i][0].first;
                ll w1=G[i][0].second;
                if(x1>x2)swap(x1,x2);
                if(M[make_pair(x1,x2)]==0){M[make_pair(x1,x2)]=++o;}//G中的边作为节点在L(G)中的编号。
                ll y1=i;
                ll y2=G[i][(ll)1].first;
                ll w2=G[i][(ll)1].second;
                if(y1>y2)swap(y1,y2);
                if(M[make_pair(y1,y2)]==0){M[make_pair(y1,y2)]=++o;}
                E[++num].u=M[make_pair(x1,x2)];
                E[num].v=M[make_pair(y1,y2)];
                E[num].w=w1+w2;
            }
            else//如果目前的节点的度大于3,那么有是用贪心策略,找到所有边中权值最小的,然后作为一个节点与其他的边连线,并存于L(G)中。
            {
                ll len=G[i].size();
                ll w1=INF;
                ll x1,x2;
                ll mid;
                for(ll j=0;j<len;j++)//寻找权值最小的边
                {
                    ll val=G[i][j].second;
                    if(val<w1)
                    {
                        x1=i;
                        x2=G[i][j].first;
                        w1=val;
                        mid=j;
                    }
                }
                if(x1>x2)swap(x1,x2);
                if(M[make_pair(x1,x2)]==0){M[make_pair(x1,x2)]=++o;}//权值最小的边在L(G)中的编号。
                for(ll j=0;j<len;j++)
                {
                    if(j==mid)continue;
                    ll y1=i;
                    ll y2=G[i][j].first;
                    ll w2=G[i][j].second;
                    if(y1>y2)swap(y1,y2);
                    if(M[make_pair(y1,y2)]==0)M[make_pair(y1,y2)]=++o;//将权值最小的边作为一个节点,与其它的边连线。
                    E[++num].u=M[make_pair(x1,x2)];
                    E[num].v=M[make_pair(y1,y2)];
                    E[num].w=w1+w2;
                }
            }
        }
        sort(E+1,E+1+num);//建立好边之后,在图E,即L(G)中寻找最小生成树。
        ll cnt=0;
        ll ans=0;
        for(int i=1;i<=num;i++)
        {
            ll x1=E[i].u,x2=E[i].v;
            x1=get(x1);x2=get(x2);
            if(x1==x2)continue;
            fa[x1]=x2;
            ans+=E[i].w;
            cnt++;
            if(cnt==n-2)break;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

第二种方法


#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define _fileout freopen("out.txt","w",stdout)
#define _filein freopen("in.txt","r",stdin)
#define ok(i) printf("ok%d\n",i)
typedef double db;
typedef long long ll;
typedef pair<ll,ll> PII;
typedef pair<pair<ll,ll>,ll> PIII;
const double PI = acos(-1.0);
const ll MOD=1e9+7;
const ll NEG=1e9+6;
const int MAXN=1e5+10ll;
const ll INF=10000000000000000ll;
const double eps=1e-9;
vector<long long> v[100005];
int main()
{
    int  t;
    scanf("%d",&t);
    while(t--)
    {
        long long ans=0;
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            v[i].clear();
        }
        for(int i=1;i<n;i++)
        {
            int temp1,temp2;
            long long temp3;
            scanf("%d%d%lld",&temp1,&temp2,&temp3);
           	v[temp1].push_back(temp3);
            v[temp2].push_back(temp3);
        }
        for(int i=1;i<=n;i++)
        {
            sort(v[i].begin(),v[i].end());
        }
        for(int i=1;i<=n;i++)
        {
            if(v[i].size()>1)
            {
                for(int j=0;j<v[i].size();j++)
                {
                    if(j==0)
                    {
                        ans+=v[i][0]*(1LL*v[i].size()-1);
                    }
                    else
                    {
                        ans+=v[i][j];
                    }
                }
            }
        }
        printf("%lld\n",ans);
    }
}

第二行是第一种方法的结果。第一行的第二种方法的结果。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值