题目链接: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);
}
}
第二行是第一种方法的结果。第一行的第二种方法的结果。