BZOJ3669[Noi2014]魔法森林——kruskal+LCT

题目描述

为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。

魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。

只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边Ei包含两个权值Ai与Bi。若身上携带的A型守护精灵个数不少于Ai,且B型守护精灵个数不少于Bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。

由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。

输入

第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。

输出

输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。

样例输入

【输入样例1】
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17
【输入样例2】
3 1
1 2 1 1

样例输出

【输出样例1】
32
【样例说明1】
如果小E走路径1→2→4,需要携带19+15=34个守护精灵;
如果小E走路径1→3→4,需要携带17+17=34个守护精灵;
如果小E走路径1→2→3→4,需要携带19+17=36个守护精灵;
如果小E走路径1→3→2→4,需要携带17+15=32个守护精灵。
综上所述,小E最少需要携带32个守护精灵。
【输出样例2】

-1
【样例说明2】
小E无法从1号节点到达3号节点,故输出-1。

提示

2<=n<=50,000

0<=m<=100,000

1<=ai ,bi<=50,000

求一个二维的最小生成树(最小生成森林)中的最大边权,一维的很好做直接按边权排序后加边即可。

那么二维的怎么搞?

我们依旧按第一维排序,这样保证了第一维一定是最小生成树,按排序后顺序枚举所有边,当边两端的点不连通就加入这条边,直接用LCT,link一下即可。

当边两端的点联通,那么我们判断当前枚举边的第二维和1到n路径上第二维边权最大值的关系,如果当前枚举的边小就把那条边删除,加上这条边。

splay要维护第二维边权的最大值,但在LCT上维护边信息并不好做,因此我们将新建一个点tmp来代表边,它的点权就是它代表边的边权。

例如x,y之间有一条边,那么在LCT中就把(x,tmp)和(y,tmp)连接起来。

每次枚举一条边之后判断1和n是否联通,只要联通不管当前边是否被选都用当前边第一维边权和1到n路径上第二维边权最大值来更新答案。

这样更新为什么是对的?

假如这条边选了并且在1到n的路径上,那么这么更新是可以的。

假如这条边选了但不在1到n的路径上,那么说明在这条边选之前1与n就是联通的,这次更新的值一定比答案大,不影响。

假如这条边没选,那这次更新的值也一定比当前答案大,一定不会影响答案。

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<cstdio>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n,m;
int ans;
int r[150010];
int v[150010];
int f[150010];
int st[150010];
int s[150010][2];
int num[150010];
struct miku
{
    int x,y;
    int a,b;
}g[1000010];
bool cmp(miku p,miku q)
{
    return p.a<q.a;
}
int get(int rt)
{
    return rt==s[f[rt]][1];
}
int is_root(int rt)
{
    return rt!=s[f[rt]][0]&&rt!=s[f[rt]][1];
}
void pushup(int rt)
{
    num[rt]=rt;
    if(v[num[s[rt][0]]]>v[num[rt]])
    {
        num[rt]=num[s[rt][0]];
    }
    if(v[num[s[rt][1]]]>v[num[rt]])
    {
        num[rt]=num[s[rt][1]];
    }
}
void pushdown(int rt)
{
    if(r[rt])
    {
        r[s[rt][0]]^=1;
        r[s[rt][1]]^=1;
        r[rt]^=1;
        swap(s[rt][0],s[rt][1]);
    }
}
void rotate(int rt)
{
    int fa=f[rt];
    int anc=f[fa];
    int k=get(rt);
    if(!is_root(fa))
    {
        s[anc][get(fa)]=rt;
    }
    s[fa][k]=s[rt][k^1];
    f[s[fa][k]]=fa;
    s[rt][k^1]=fa;
    f[fa]=rt;
    f[rt]=anc;
    pushup(fa);
    pushup(rt);
}
void splay(int rt)
{
    int top=0;
    st[++top]=rt;
    for(int i=rt;!is_root(i);i=f[i])
    {
        st[++top]=f[i];
    }
    for(int i=top;i>=1;i--)
    {
        pushdown(st[i]);
    }
    for(int fa;!is_root(rt);rotate(rt))
    {
        if(!is_root(fa=f[rt]))
        {
            rotate(get(fa)==get(rt)?fa:rt);
        }
    }
}
void access(int rt)
{
    for(int x=0;rt;x=rt,rt=f[rt])
    {
        splay(rt);
        s[rt][1]=x;
        pushup(rt);
    }
}
void reverse(int rt)
{
    access(rt);
    splay(rt);
    r[rt]^=1;
}
int find(int rt)
{
    access(rt);
    splay(rt);
    while(s[rt][0])
    {
        rt=s[rt][0];
    }
    return rt;
}
void link(int x,int y)
{
    reverse(x);
    f[x]=y;
}
void cut(int x,int y)
{
    reverse(x);
    access(y);
    splay(y);
    f[x]=0;
    s[y][0]=0;
    pushup(y);
}
int main()
{
    ans=1<<30;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        num[i]=i;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&g[i].x,&g[i].y,&g[i].a,&g[i].b);
    }
    sort(g+1,g+1+m,cmp);
    for(int i=1;i<=m;i++)
    {
        int x=g[i].x;
        int y=g[i].y;
        if(find(x)!=find(y))
        {
            v[n+i]=g[i].b;
            num[n+i]=n+i;
            link(x,n+i);
            link(y,n+i);
        }
        else
        {
            reverse(x);
            access(y);
            splay(y);
            int val=num[y];
            if(v[val]>g[i].b)
            {
                cut(val,g[val-n].x);
                cut(val,g[val-n].y);
                num[n+i]=n+i;
                v[n+i]=g[i].b;
                link(n+i,x);
                link(n+i,y);
            }
        }
        if(find(1)==find(n))
        {
            reverse(1);
            access(n);
            splay(n);
            ans=min(ans,g[i].a+v[num[n]]);
        }
    }
    if(ans==(1<<30))
    {
        printf("-1");
        return 0;
    }
    printf("%d",ans);
}
原文地址:https://www.cnblogs.com/Khada-Jhin/p/9745446.html