zoukankan      html  css  js  c++  java
  • BZOJ 3669 【NOI2014】 魔法森林

    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。

    HINT

    2<=n<=50,000

    0<=m<=100,000

    1<=ai ,bi<=50,000

      话说这道题被神犇xzy推荐给我写spfa入门题,然后狂TLE不止...把这道题当成入门题给我我也是醉了......

      这一题其实我们只需将边按a权值排序,再一条边一条边地插入,每次跑一遍最短路即可轻松AC。(o_o)

      好吧,其实spfa还是我很久以前写的了。显然裸的spfa肯定是跑不过的,于是我们需要加一点小小的优化,那就是每次不清空dis数组,改为直接将新边连接的两个点加入队列,跑一遍spfa(话说我也不知道这为什么复杂度是对的【其实是不对的】)就可以AC了。

      UPD:已经在UOJ上被卡掉了

      下面贴代码:

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstdlib>
     4 #include<cstring>
     5 #include<algorithm>
     6 #include<cmath>
     7 #define maxn 100001
     8 #define INF 2147483647
     9 
    10 using namespace std;
    11 typedef long long llg;
    12 
    13 struct data{
    14     int t,f,c1,c2;
    15     bool operator < (const data &h)const{return c1<h.c1;}
    16 }s[maxn];
    17 int head[maxn],to[maxn<<1],next[maxn<<1],l,r;
    18 int c[maxn<<1],dis[maxn],d[maxn],n,m,tt,ans;
    19 bool w[maxn];
    20 
    21 int getint(){
    22     int w=0,q=0;
    23     char c=getchar();
    24     while((c<'0'||c>'9')&&c!='-') c=getchar();
    25     if(c=='-') q=1,c=getchar();
    26     while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
    27     return q?-w:w;
    28 }
    29 
    30 void spfa(){
    31     d[r++]=1;w[1]=1;
    32     while(l!=r){
    33         int u=d[l++];l%=maxn;w[u]=0;
    34         for(int i=head[u],v;v=to[i],i;i=next[i])
    35             if(dis[v]==-1 || dis[v]>max(dis[u],c[i])){
    36                 dis[v]=max(dis[u],c[i]);
    37                 if(!w[v]){
    38                     w[v]=1;d[r++]=v;
    39                     r%=maxn;
    40                 }
    41             }
    42     }
    43 }
    44 
    45 int main(){
    46     n=getint();m=getint();
    47     memset(dis,-1,sizeof(dis));
    48     for(int i=1;i<=m;i++){
    49         s[i].t=getint();s[i].f=getint();
    50         s[i].c1=getint();s[i].c2=getint();
    51     }
    52     sort(s+1,s+m+1);ans=INF;dis[1]=0;
    53     for(int i=1;i<=m;i++){
    54         int f=s[i].f,t=s[i].t;
    55         to[++tt]=f;next[tt]=head[t];head[t]=tt;
    56         to[++tt]=t;next[tt]=head[f];head[f]=tt;
    57         c[tt-1]=c[tt]=s[i].c2;l=r=0;
    58         if(dis[t]!=-1) d[r++]=t,w[t]=1;
    59         if(dis[f]!=-1) d[r++]=f,w[f]=1;
    60         spfa();
    61         if(dis[n]!=-1) ans=min(ans,dis[n]+s[i].c1);
    62     }
    63     if(ans==INF) printf("-1");
    64     else printf("%d",ans);
    65 }
    View Code

      下面我还是准备讲讲复杂度很对的一种做法(虽然跑的更慢)。我们仍然将边按a权值排序,然后我们只要快速求出1到n的最小生成树(或者最短路也可以)。虽然spfa可以跑过,但这个算法复杂度毕竟不对,我们还是要追求一种复杂度比较对的算法。其实,我们只要动态维护最小生成树就可以了。那么用什么呢?CDQ图分治或许可以,但我选择了LCT。其实以前我对于边带权值的动态树我一直是懵逼的,直到orz了hzwer大神的题解,我才发现了一种极其优秀的做法。我们可以把边也建成节点,而且只有边所代表的节点才有权值。这样就非常好做了是不?我们splay上维护一个区间最大值不就可以了是么?剩下的就是link-cut-tree的模板了。

      我觉得这道题是一道LCT的好题。如果你还不会LCT,请前往:[Hnoi2010]Bounce 弹飞绵羊 [Sdoi2008]Cave 洞穴勘测

      如果你会LCT但是就是A不了这题,推荐你还是仔细啃一啃这道题的代码吧。

      这道题AC代码(LCT)如下:

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<algorithm>
      5 #include<cmath>
      6 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
      7 #define maxn 150010
      8 #define maxm 100010
      9 
     10 using namespace std;
     11 typedef long long llg;
     12 
     13 struct data{
     14     int u,v,a,b;
     15     bool operator < (const data &h)const{return a<h.a;}
     16 }ss[maxm];
     17 int val[maxn],s[maxn][2],fa[maxn],maxv[maxn],d[maxn],ans,n,m;
     18 bool rev[maxn];
     19 
     20 int getint(){
     21     int w=0;bool q=0;
     22     char c=getchar();
     23     while((c>'9'||c<'0')&&c!='-') c=getchar();
     24     if(c=='-') q=1,c=getchar();
     25     while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
     26     return q?-w:w;
     27 }
     28 
     29 bool isroot(int x){return x!=s[fa[x]][0] && x!=s[fa[x]][1];}
     30 int findrt(int x){while(fa[x]) x=fa[x];return x;}
     31 
     32 void update(int x){
     33     int l=s[x][0],r=s[x][1];maxv[x]=x;
     34     if(val[maxv[l]]>val[maxv[x]]) maxv[x]=maxv[l];
     35     if(val[maxv[r]]>val[maxv[x]]) maxv[x]=maxv[r];
     36 }
     37 
     38 void R(int x){
     39     int p=fa[x],g=fa[p];
     40     bool l=(x==s[fa[x]][0]),r=!l;
     41     if(!isroot(p)) s[g][p==s[g][1]]=x;
     42     fa[s[x][l]]=p; s[p][r]=s[x][l];
     43     fa[p]=x; s[x][l]=p; fa[x]=g;
     44     update(p); update(x);
     45 }
     46 
     47 void splay(int x){ //将x旋到当前splay的根节点
     48     d[d[0]=1]=x;
     49     for(int i=x;!isroot(i);i=fa[i]) d[++d[0]]=fa[i];
     50     for(int i=d[0],u;u=d[i],i;i--)
     51         if(rev[u]){
     52             swap(s[u][0],s[u][1]);rev[u]=0;
     53             rev[s[u][0]]^=1;rev[s[u][1]]^=1;
     54         }
     55     while(!isroot(x)){
     56         int p=fa[x],g=fa[p];
     57         if(!isroot(p)){
     58             if((x==s[p][0])^(p==s[g][0])) R(x);
     59             else R(p);
     60         }
     61         R(x);
     62     }
     63 }
     64 
     65 void access(int u){ //将u到splay根节点的路径打通
     66     for(int t=0;u;t=u,u=fa[u])
     67         splay(u),s[u][1]=t,update(u);
     68 }
     69 
     70 void makert(int u){ //把u换到它所在splay的根,并把沿途路径反向
     71     access(u);
     72     splay(u);rev[u]^=1;
     73 }
     74 
     75 int query(int u,int v){ //查询u到v的边中的最大值
     76     makert(u);access(v);splay(v);
     77     return maxv[v];
     78 }
     79 
     80 void cut(int u,int v){ //把u和v的连接断掉,谁是父亲无关紧要
     81     makert(u);access(v);splay(v);
     82     s[v][0]=fa[u]=0; update(v);
     83 }
     84 
     85 void link(int u,int v){ //把u的父亲变成v
     86     makert(u);fa[u]=v;
     87 }
     88 
     89 int main(){
     90     File("a");
     91     n=getint();m=getint();
     92     for(int i=1;i<=m;i++){
     93         ss[i].u=getint();ss[i].v=getint();
     94         ss[i].a=getint();ss[i].b=getint();
     95     }
     96     sort(ss+1,ss+m+1);ans=(1<<30);
     97     for(int i=1,u,v,t;i<=m;i++){
     98         u=ss[i].u;v=ss[i].v;
     99         if(findrt(u)==findrt(v)){
    100             t=query(u,v);
    101             if(val[t]>ss[i].b){
    102                 cut(ss[t-n].u,t);
    103                 cut(ss[t-n].v,t);
    104             }
    105             else continue;
    106         }
    107         val[i+n]=ss[i].b;maxv[i+n]=i+n;
    108         link(u,i+n);link(v,i+n);
    109         if(findrt(1)==findrt(n))
    110             ans=min(ans,ss[i].a+val[query(1,n)]);
    111     }
    112     printf(ans==(1<<30)?"-1":"%d",ans);
    113     return 0;
    114 }
    View Code
  • 相关阅读:
    bzoj1415 NOI2005聪聪和可可
    Tyvj1952 Easy
    poj2096 Collecting Bugs
    COGS 1489玩纸牌
    COGS1487 麻球繁衍
    cf 261B.Maxim and Restaurant
    cf 223B.Two Strings
    cf 609E.Minimum spanning tree for each edge
    cf 187B.AlgoRace
    cf 760B.Frodo and pillows
  • 原文地址:https://www.cnblogs.com/lcf-2000/p/5571214.html
Copyright © 2011-2022 走看看