bzoj #3669 魔法森林(SPFA/LCT)(NOI2014)

标签:SPFA/LCT
Time Limit: 30 Sec  Memory Limit: 512 MB
Submit: 3139  Solved: 1984
[Submit][Status][Discuss]

Description

为了得到书法大家的真传,小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型守护精灵的个数之和。

Input

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

Output

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

Sample Input


【输入样例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







Sample Output


【输出样例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。

这题看起来很难,不过有一种很简单的水法(不想看的话可以直接跳到下面粗体字)
我们把所有边按照Ai为第一关键字,Bi为第二关键字排序,每加一条边,就跑一次SPFA(当然,dis数组维护的是最大值),用dis[n]+a[i](i为当前加的边去更新答案)。
这种做法的正确性显而易见,然而复杂度确是O(nmk),TLE!
于是新技能get:动态加边SPFA,就是跑完一遍SPFA后,不把dis数组清空,下次加边只将边所连接的两个点加入队列去更新答案,复杂度O(玄学)。
附上代码(比正解还快!)

/**************************************************************
    Problem: 3669
    User: P1atform
    Language: C++
    Result: Accepted
    Time:3988 ms
    Memory:50512 kb
****************************************************************/

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define N 100050
#define M 200050
#define Q 10000050 
#define INF 1000000000
#define add(u,v,vv) to[++top]=head[u],head[u]=top,w[top]=v,val[top]=vv
#define For(x) for(int h=head[x],o=w[h],v=val[h];h;o=w[h=to[h]],v=val[h])
using namespace std;
struct edge
{
    int u,v,a,b;
    bool operator <(edge y) const {if (a==y.a) return b<y.b;return a<y.a;}
}t[M];
int q[Q],bo[N],ans=INF,d[N],l,r,n,m,top=0,to[M*2],head[M*2],w[M*2],val[M*2],x;
inline void read(int &x)
{
    x=0;
    char ch=getchar();
    int f=1;
    while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    x=x*f; 
}
int main()
{
    read(n),read(m);
    for (int i=1;i<=m;i++) read(t[i].u),read(t[i].v),read(t[i].a),read(t[i].b);
    sort(t+1,t+m+1),memset(d,0x7f,sizeof(d)),memset(bo,0,sizeof(bo)),l=0,r=-1,d[1]=0;
    for (int i=1;i<=m;i++)
    {
        add(t[i].u,t[i].v,t[i].b),add(t[i].v,t[i].u,t[i].b);
        if (!bo[t[i].u]) bo[t[i].u]=1,q[++r]=t[i].u;
        if (!bo[t[i].v]) bo[t[i].v]=1,q[++r]=t[i].v;
        if (t[i].a==t[i-1].a&&t[i].b==t[i-1].b) continue;
        while (l<=r) 
        {
            x=q[l++];
            For(x) if (d[o]>max(d[x],v)) 
            {
                d[o]=max(d[x],v);
                if (!bo[o]) bo[o]=1,q[r=(r+1)%Q]=o;
            }
            bo[x]=0;
        }
        ans=min(ans,d[n]+t[i].a);
    }
    if (ans==INF) printf("-1\n");else printf("%d\n",ans);
    return 0;
}

一眼可以看出,这道题的正解是LCT。

基于上述的思路,我们同样可以先把边排序。LCT维护动态的生成树,用w[i]表示当前路径b值最大的边。然后用并查集维护连通性,如果即将加边的两个点已经连接,则判断当前路径上权值最大的边是否比当前边的b值大。最后如果1和n联通更新答案即可。
新技能get:由于LCT只能维护点,这里的处理方法就是把一条边拆成一个点加上两条边即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define N 100050
#define INF 1000000000
using namespace std;
typedef long long ll;
struct edge
{
    int u,v;
    ll a,b;
    bool operator <(edge x) const{if (a==x.a) return b<x.b;return a<x.a;}
}t[N*2];
int val[N*2],fa[N*2],n,m,fx,fy,k;
ll ans=INF;
struct lct
{
    int s[N*2][2],f[N*2],lz[N*2],top,q[N*2];
    ll w[N*2];
    inline bool isroot(int x) {return x!=s[f[x]][0]&&x!=s[f[x]][1];}
    inline void pushup(int x)
    {
        if (!x) return;
        w[x]=x;
        if (s[x][0]) if (val[w[x]]<val[w[s[x][0]]]) w[x]=w[s[x][0]];
        if (s[x][1]) if (val[w[x]]<val[w[s[x][1]]]) w[x]=w[s[x][1]];
    }
    inline void pushdown(int x) {if (x&&lz[x]) swap(s[x][0],s[x][1]),lz[s[x][0]]^=1,lz[s[x][1]]^=1,lz[x]=0;}
    inline void rotate(int x,int k)
    {
        int y=f[x],z=f[y];
        s[y][!k]=s[x][k],f[x]=z;
        if (s[x][k]) f[s[x][k]]=y;
        if (!isroot(y)) s[z][y==s[z][1]]=x;
        s[x][k]=y,f[y]=x,pushup(y),pushup(x);
    }
    inline void splay(int x)
    {
        top=0,q[++top]=x;
        for (int i=x;!isroot(i);i=f[i]) q[++top]=f[i];
        for (int i=top;i;i--) pushdown(q[i]); 
        while (!isroot(x))
        {
            int y=f[x],z=f[y];
            if (isroot(y)) {rotate(x,x==s[y][0]);continue;}
            if (y==s[z][0])
            {
                if (x==s[y][0]) rotate(y,1),rotate(x,1);
                else rotate(x,0),rotate(x,1);
            }
            else
            {
                if (x==s[y][1]) rotate(y,0),rotate(x,0);
                else rotate(x,1),rotate(x,0);
            }
        }
    }
    inline void access(int x) {for (int i=0;x;i=x,x=f[x]) splay(x),s[x][1]=i,pushup(x);}
    inline void makeroot(int x) {access(x),splay(x),lz[x]^=1;}
    inline void split(int x,int y) {makeroot(x),access(y),splay(y);}
    inline void link(int x,int y) {makeroot(x),f[x]=y;}
    inline void cut(int x,int y) {split(x,y);if (x==s[y][0]) f[x]=0,s[y][0]=0;}
}T;
inline int find(int x) {if (fa[x]==x) return x;else return fa[x]=find(fa[x]);}
template <class _T> inline void read(_T &x)
{
    x=0;
    int f=0;
    char ch=getchar();
    while (!isdigit(ch)) {if (ch=='-') f=1;ch=getchar();}
    while (isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    if (f) x=-x;
}
int main()
{
    read(n),read(m);
    for (int i=1;i<=m;i++) read(t[i].u),read(t[i].v),read(t[i].a),read(t[i].b);
    sort(t+1,t+m+1);
    for (int i=1;i<=m;i++) val[n+i]=t[i].b;
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int i=1;i<=m;i++)
    {
        fx=find(t[i].u),fy=find(t[i].v);
        if (fx!=fy) T.link(t[i].u,n+i),T.link(n+i,t[i].v),fa[fx]=fy;
        else
        {
            T.split(t[i].u,t[i].v),k=T.w[t[i].v];
            if (val[k]>t[i].b) T.cut(t[k-n].u,k),T.cut(k,t[k-n].v),T.link(t[i].u,i+n),T.link(i+n,t[i].v);
        }
        if (find(1)==find(n)) T.split(1,n),ans=min(ans,t[i].a+val[T.w[n]]);
    }
    if (ans==INF) ans=-1;
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值