题解 P3037 【[USACO11DEC]简化的农场Simplifying the Farm】

· · 题解

思路

如果没有边权相同的边,那么其实时不存在多种最小生成树的方案的。

题目中同一边权的个数不超过三个

按照Kruskal的建树流程进行模拟。

边权排完序后,边权相同的会聚到一起,然后在从小到大枚举的时候分类讨论即可。

流程

首先找到边权相同到的几条边。

这是我们需要在这些边中选出有用边。

所谓有用边就是加入其中一条边,不会产生环。因为之前已经加入的边是不能再反悔的,所以如果当前等待加入的边与之前的边存在矛盾的话,当前边一定是无用的。 如下图,下图的橙色边就是无用边:

然后我们考虑哪些情况会使最小生成树方案增加:

代码

使用set帮助区分三条边权相同时,第一种情况和第二种情况。

/*!
 * FileName: luogu-3037.cpp
 * This Problem is on luogu. The ID of the problem is 3037. 
 * Copyright(c) 2019 Shu_Yu_Mo
 * MIT Licensed
 * Luogu: https://www.luogu.org/space/show?uid=44615
 * Github: https://github.com/oldsuold/
 * Gitee: https://gitee.com/Shu_Yu_Mo/
 * These words were created by an amazing tool on 2019-08-03 22:47:58 written by Shu_Yu_Mo.
 */
#include<cstdio>
#include<cstring>
#include<iostream>
#include<set>
#include<algorithm>
#define inf 0x7fffffff
using namespace std;
const int _M = 1e5 + 100;
const int _N = 4e4 + 100;
const int MOD = 1000000007;
inline int read()
{
    char c = getchar(); int sign = 1; int x = 0;
    while(c > '9' || c < '0') { if(c=='-')sign = -1; c = getchar(); } 
    while(c <= '9' && c >= '0') { x *= 10; x += c - '0'; c = getchar(); }
    return x * sign;
}
struct edges{
    int u;
    int v;
    int w;
    bool operator < (const edges & A) const {
        return w < A.w;
    }
}E[_M];
int n, m;
//Kruskal 所用的并查集   Start
int f[_N];
int find(int x){ return f[x] == x ? x : f[x] = find(f[x]); }
void initForSet()
{
    for(register int i = 1;i <= n;i++)
        f[i] = i;
}
void marge(int x, int y)
{
    x = find(x);
    y = find(y);
    if(x == y) return;
    f[x] = y;
}
bool ask(int x, int y)
{
    return find(x) == find(y);
}
//Kruskal 所用的并查集   End
int nxtIt(int now)//往下找最后一个边权相同的位置
{
    for(register int i = now;i <= m;i++)
        if(E[now].w != E[i].w)
            return i - 1;
    return m;
}
set <pair<int , int >  >  S;//
int main()
{
    n = read(), m = read();
    for(register int i = 1;i <= m;i++)
    {
        E[i].u = read();
        E[i].v = read();
        E[i].w = read(); 
    }
    initForSet();
    sort(E + 1, E + 1 + m);
    int ans1 = 0;
    int ans2 = 1;
    int nxt;
    for(register int i = 1;i <= m;)
    {
        S.clear();
        nxt = nxtIt(i);
        int totEdge = 0;
        for(register int j = i;j <= nxt;j++)
            if(!ask(E[j].u, E[j].v))
            {
                totEdge ++;
                int k1 = min(find(E[j].u), find(E[j].v));
                int k2 = max(find(E[j].u), find(E[j].v));
                S.insert(make_pair(k1, k2));
            }
        int totAdd = 0;
        for(register int j = i;j <= nxt;j++)
        {
            if(!ask(E[j].u, E[j].v))
            {
                marge(E[j].u, E[j].v);
                totAdd ++;
                ans1 = (ans1 + E[j].w) % MOD;
            }
        }
        if(totAdd == 1)
            ans2 = ans2 * 1LL * totEdge % MOD;//分别是两条边权相同时的第一种情况    和    三条边权相同时的第三种情况。
        if(totAdd == 2 && totEdge == 3)
        {
            if(S.size() == 3) ans2 = ans2 * 3LL % MOD;//三条边权相同时的第一种情况
            if(S.size() == 2) ans2 = ans2 * 2LL % MOD; //三条边权相同时的第二种情况
        }
        i = nxt + 1;
    }
    printf("%d %d\n", ans1 % MOD, ans2 % MOD);
    return 0;
}