先来看看我一开始以为这个题是什么吧。因为买下来的路就可以来回走动,所以就是买下来最小生成树就是代价,这是没有选择免费边的情况。如果能免费一条边呢,我们连出一个缺少最长边的最小生成树,保证代价最小,然后再连入最后一条边,如果最后一条边能连出整棵树,那么代价一定就是最小的,这样的情况有多少也很好计算。很可惜必不是正解,举出反例,如果最长边有好几条,这样的话绝对只会计入一种的情况,至于分类再枚举最长边讨论就会又慢又麻烦,所以不可取。
下面来说正解,试想,如果我们有一棵最小生成树,那么任意多出一条边(我们称为边a)都会连出一个环,那么再在这个环里树上任意拆除另一条边(边b)都会回到树形结构,就贪心的思想而言应当去除两条最长边。按照之前的假设,若这里环上的另一条边b应当是树上的最大边,才能保证剩余树结构的代价最少。
此时让我们称边a和边b是等价的,任意免费其中一边并不连接另外一边都能算合法情况。且环上一边a理应大于等于树上边b,不然不满足原本的最小生成树结构,所以就不用再考虑边a的情况了,反正必定大于等于,条件就只剩下了树上边b为树上最大边这个要求。
本题就变成了,找任意一边与树相连构成的环中,在树上的边存在树上最大边的次数,如果是树边的话就是己边的长度应当等于最大边。我们确定了全树最大边后,要求的就是部分树结构的最大边,这样的操作就可以用树上倍增LCA或者树链剖分来做,(不就是数据结构的B题解法?)
kruskal重构树 + 倍增lca,AC代码如下:
#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 200005
#define maxm 55
#define hrdg 1000000007
#define zh 16711680
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define int long long
using namespace std;
const double eps = 1e-7;
int n, m, u, v, d;
struct E {int u, v, w;} e[maxn<<1];
struct Edge {int to, nex, w;} edge[maxn<<1];
int head[maxn<<1], tot;
void add_edge(int u, int v, int w) {edge[++tot] = {v, head[u], w}; head[u] = tot;} //前向星
bool cmp(E x, E y) {return x.w < y.w;}
int root, judge, start, cnt, sum = 0;
int f[maxn][21], w[maxn][21], depth[maxn];
bool vis[maxn];
inline int read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int fa[maxn]; //并查集三连
inline int setfind(int x){
return x == fa[x] ? x : fa[x] = setfind(fa[x]);
}
inline void setunion(int x,int y){
x = setfind(fa[x]);
y = setfind(fa[y]);
fa[x] = y;
}
inline bool setcheck(int x,int y){
x = setfind(x);
y = setfind(y);
return x == y;
}
bool lca(int x, int y) //倍增表判断
{
int ret = 0;
if(depth[x] > depth[y]) swap(x, y);
for(int i=20; i>=0; i--)
if(depth[f[y][i]] >= depth[x])
{
ret = max(ret, w[y][i]);
y = f[y][i];
}
if(x==y){
return ret == judge;
}
for(int i=20; i>=0; i--)
if(f[x][i] != f[y][i])
{
ret = max(ret, max(w[x][i], w[y][i]));
x = f[x][i];
y = f[y][i];
}
ret = max(ret, max(w[x][0], w[y][0]));
return ret == judge;
}
void dfs(int now) //通过递归重构
{
vis[now] = true;
for (int i = head[now]; i; i = edge[i].nex)
{
int to = edge[i].to;
if (vis[to]) continue;
depth[to] = depth[now] + 1;
f[to][0] = now;
w[to][0] = edge[i].w;
dfs(to);
}
}
void kruskal() //最小生成树
{
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++)
{
if(setcheck(e[i].u, e[i].v))
continue;
setunion(e[i].u, e[i].v);
add_edge(e[i].u, e[i].v, e[i].w);
add_edge(e[i].v, e[i].u, e[i].w);
cnt++;
if (cnt != n - 1)
sum += e[i].w;
if (cnt == n - 1)
{
judge = e[i].w;
break;
}
}
}
void init()
{
root = e[1].u;
depth[root] = 1;
f[root][0] = root;
w[root][0] = 0;
}
signed main()
{
n = read(); m = read();
for (int i = 1; i <= m; i++)
{
u = read(); v = read(); d = read();
e[i] = {u, v, d};
}
sort(e + 1, e + 1 + m, cmp); //排序后kruskal
kruskal();
init();
dfs(root);
for (int i = 1; i <= 20; i++)
for (int j = 1; j <= n; j++)
{
f[j][i] = f[f[j][i - 1]][i - 1];
w[j][i] = max (w[j][i - 1], w[f[j][i - 1]][i - 1]); //倍增递推
}
int ans = 0;
for (int i = 1; i <= m; i++)
if (lca(e[i].u, e[i].v))
ans++;
printf("%lld %lld", sum, ans);
return 0;
}