洛谷P4180 严格次小生成树

  • 题意就是求严格次小生成树。
  • 其实思路挺明确的,跟非严格次小生成树差不多。
  • 那么先回忆一下非严格次小生成树:先求出最小生成树,再利用Kruskal的贪心思路,对每一条非树边求它连成的环上最大的一条边,用这条非树边替换掉,对于替换掉每一条边后的树权值取min就是答案。
  • 严格与非严格的区别:不能相等了。所以好像只求最大值会出问题,如果一个环上有两条一样长的边,得到的结果就是非严格的。所以改进算法,倍增时记录权值的最大值和次大值,保证最大严格大于次大。然后几乎用相同的打法就打出来了(然而WA了很久)

代码(打了注释):

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int INF = 2e18+10;
const int N = 1e5+10;
const int M = 4e5+10;
const int E = 20;
int n, m, sum;
struct PREEDGE{//pre_edge是图上的边
    int u, v, w;
    bool on_tree;
    //重载小于号要打
    bool operator < (const PREEDGE &x)const{return w < x.w;}
}pre_edge[M];
int fa[N];
struct TREEEDGE{//最小生成树的树边
    int nxt, v, w;
}tree_edge[N*2];
int point[N], e;
int dpt[N], f[N][E+1], fst[N][E+1], scd[N][E+1], ans;

void add_edge(int u, int v, int w)
{
    tree_edge[++e] = (TREEEDGE){point[u], v, w};
    point[u] = e;
}

int FIND(int u)
{
    if (fa[u] == u) return u;
    return fa[u] = FIND(fa[u]);
}

bool cmp(int x, int y){return x > y;}
inline int max(int x, int y){return x > y ? x : y;}
inline int min(int x, int y){return x < y ? x : y;}

int SCD(int x, int y, int z, int w)//取四个数的次大值
{
    int a[4] = {x, y ,z, w}, fstmax = -INF, scdmax = -INF;
    for (int i = 0; i < 4; i++)
        fstmax = max(fstmax, a[i]);
    for (int i = 0; i < 4; i++)
        if (a[i] != fstmax)
            scdmax = max(scdmax, a[i]);
    return scdmax;
}

void DFS(int u, int fa)
{
    dpt[u] = dpt[fa]+1;
    for (int i = 1; i <= E; i++){
        f[u][i] = f[f[u][i-1]][i-1];
        fst[u][i] = max(fst[u][i-1], fst[f[u][i-1]][i-1]);
        scd[u][i] = SCD(fst[u][i-1], fst[f[u][i-1]][i-1], scd[u][i-1], scd[f[u][i-1]][i-1]);
    }
    for (int i = point[u]; i != -1; i = tree_edge[i].nxt){
        int v = tree_edge[i].v;
        int w = tree_edge[i].w;
        if (v == fa) continue;
        f[v][0] = u;//一开始这句话忘记打,然而过了洛谷的样例,懵逼提交了好几次
        fst[v][0] = w;
        DFS(v, u);
    }
}

void UPDATE(int &x, int &y, int z)//更新最大值和次大值
{
    if (z > x) y = x, x = z;
    else if (z != x && z > y) y = z;
}

void LCA(int x, int y, int &fstmax, int &scdmax)
{
    //注释掉的是原来的思路,开一个vector,把所有路上的最大值和次大值丢进去,sort加unique,然而本来我们只需要记录两个值,这样做浪费了很多时间
    // vector<int> aa;
    // aa.clear();
    fstmax = scdmax = -INF;
    if (dpt[x] < dpt[y]) swap(x, y);
    for (int i = E; i >= 0; i--)
        if (dpt[x]-(1<<i) >= dpt[y]){
            UPDATE(fstmax, scdmax, fst[x][i]);
            UPDATE(fstmax, scdmax, scd[x][i]);
            x = f[x][i];
        }
    for (int i = E; i >= 0; i--)
        if (f[x][i] != f[y][i]){
            UPDATE(fstmax, scdmax, fst[x][i]);
            UPDATE(fstmax, scdmax, scd[x][i]);
            UPDATE(fstmax, scdmax, fst[y][i]);
            UPDATE(fstmax, scdmax, scd[y][i]);
            x = f[x][i];
            y = f[y][i];
        }
    if (x != y){
        UPDATE(fstmax, scdmax, fst[x][0]);
        UPDATE(fstmax, scdmax, scd[x][0]);
        UPDATE(fstmax, scdmax, fst[y][0]);
        UPDATE(fstmax, scdmax, scd[y][0]);
    }
    // sort(aa.begin(), aa.end(), cmp);
    // unique(aa.begin(), aa.end());
}

main()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= m; i++){
        scanf("%lld%lld%lld", &pre_edge[i].u, &pre_edge[i].v, &pre_edge[i].w);
        pre_edge[i].on_tree = 0;
    }
    sort(pre_edge+1, pre_edge+m+1);
    sum = 0;
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    memset(point, -1, sizeof(point)); e = 0;
    for (int i = 1; i <= m; i++){
        int u = pre_edge[i].u;
        int v = pre_edge[i].v;
        int w = pre_edge[i].w;
        int fau = FIND(u);
        int fav = FIND(v);
        if (fau != fav){
            fa[fau] = fav;
            sum += w;
            pre_edge[i].on_tree = 1;
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
    }
    memset(fst, ~0x3f, sizeof(fst));
    memset(scd, ~0x3f, sizeof(scd));
    memset(f, 0, sizeof(f)); dpt[0] = 0;
    DFS(1, 0);
    ans = INF;
    for (int i = 1; i <= m; i++)
        if (!pre_edge[i].on_tree){
            int u = pre_edge[i].u;
            int v = pre_edge[i].v;
            int w = pre_edge[i].w;
            int fstmax, scdmax;
            LCA(u, v, fstmax, scdmax);
            if (w != fstmax) ans = min(ans, w-fstmax);
            else ans = min(ans, w-scdmax);
        }
    printf("%lld", ans+sum);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyyxyyx/article/details/81412306