【NOI2014】魔法森林

Description

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

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

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

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

Solution

长得好像双关键字的最小生成树

但是只是把和改成了边上的最大值而已。
往这个方面去想。

怎么做

mst的克鲁斯卡尔算法是先排个序,那么我们也按第一个关键字排个序。
逐个加边,假如出现了一个环该怎么办?

贪心的想一下

我们删去换上b值最大的一条边不就好了!
不过楼主并不知道为什么,就像楼主对克鲁斯卡尔算法的证明也懵懵懂懂(当个黑盒算法用)。

答案怎么求

因为我们的a值是排序后逐个加进去的,所以当前的最大值就是a[i]。那么用并查集判断如果1和n在同一个集合,那么就用a[i]加上b的最大值来更新答案。

怎么维护这个最大值

发现需要删边,加边,维护,而且没有环,是一棵树。
那么用Link Cut Tree就好了。

Code

#include
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
const int maxn=200007;
int i,j,k,l,n,m,ans,x,y,u,v;
int t[maxn][2],mx[maxn],f[maxn],fa[maxn],pfa[maxn],wei[maxn],key[maxn],lca,d[maxn];
bool bz[maxn];
struct node{
    int x,y,a,b;
}a[maxn];
bool cmp(node x,node y){
    return x.aint gf(int x){
    if(!fa[x])return x;
    fa[x]=gf(fa[x]);return fa[x];
}
bool son(int x){if(t[f[x]][0]==x)return 0;return 1;}
void update(int x){
    int l=wei[t[x][0]],r=wei[t[x][1]];
    if(key[l]>key[r])wei[x]=l;else wei[x]=r;
    if(key[x]>key[l]&&key[x]>key[r])wei[x]=x;   
}
void rotate(int x){
    int y=f[x],z=son(x);
    t[y][z]=t[x][1-z];
    if(t[x][1-z])f[t[x][1-z]]=y;f[x]=f[y];
    if(f[x])t[f[x]][son(y)]=x;else pfa[x]=pfa[y],pfa[y]=0;
    t[x][1-z]=y;f[y]=x;
    update(y);update(x);
}
void down(int x){
    if(bz[x]){
        swap(t[x][0],t[x][1]);
        if(t[x][0])bz[t[x][0]]^=1;if(t[x][1])bz[t[x][1]]^=1;
        bz[x]=0;
    }
}
void remove(int x,int y){
    do{
        d[++d[0]]=x;
        x=f[x];
    }while(x!=y);    
    while(d[0])down(d[d[0]--]);
}
void splay(int x,int y){
    remove(x,y);
    while(f[x]!=y){
        if(f[f[x]]!=y)if(son(f[x])==son(x))rotate(f[x]);else rotate(x);
        rotate(x);
    }
}
void access(int x){
    int y=0;
    while(x){
        splay(x,0);
        f[t[x][1]]=0,pfa[t[x][1]]=x;
        t[x][1]=y,f[y]=x;
        pfa[y]=0;
        update(x);
        y=x,x=pfa[x];
    }lca=y;
}
void makeroot(int x){
    access(x);
    splay(x,0);
    bz[x]^=1;
}
void link(int x,int y){
    makeroot(x);
    pfa[x]=y;
}
void cut(int x,int y){
    makeroot(x);
    access(y);
    splay(y,0);
    t[y][0]=0,f[x]=pfa[x]=0;
    update(y);
}
int find(int x,int y){
    makeroot(x);
    access(y);
    splay(y,0);
    return wei[y];
}
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,m){
        scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].a,&a[i].b);
    }
    sort(a+1,a+1+m,cmp);
    ans=0x7fffffff;
    fo(i,1,m)key[i+n]=a[i].b,wei[i+n]=i+n;
    fo(i,1,m){
        x=a[i].x,y=a[i].y;u=gf(x),v=gf(y);
        if(u!=v){
            fa[v]=u;
            link(x,i+n);
            link(y,i+n);
        }
        else{
            int o=find(x,y);
            if(key[o]>a[i].b){
                cut(a[o-n].x,o);
                cut(a[o-n].y,o);
                link(x,i+n);
                link(y,i+n);
            }
        }
        u=gf(1),v=gf(n);
        if(u==v)ans=min(ans,a[i].a+key[find(1,n)]);
    }
    if(ans==0x7fffffff)printf("-1\n");
    else printf("%d\n",ans);
}

你可能感兴趣的:(noi,LCT,动态树,最小生成树,并查集)