• 【NOI2014T2】魔法森林-LCT维护最小生成树


    测试地址:魔法森林
    做法:这题真的是神,虽然想到了最小生成树,但是只想到枚举其中一个变量的方法,原来LCT还可以这样用……但是听说这一题好像可以用各种玄学方法骗到满分,这里就不耍小聪明了。
    这一题的标准解法是用LCT维护最小生成树。
    我们乍一看这题,如果只有一种守护精灵,那么直接做个最小生成树或者跑个类似SPFA的BFS即可,可是这一题存在两种守护精灵,答案是两种守护精灵使用量的和,这时候我们就要考虑其他的方法。
    有的同学可能想到二分第一种守护精灵的数量,然后对第二种跑最小生成树,然后blablabla。很遗憾,这种想法是错的,反例很容易可以找出来。那么我们只能枚举第一种守护精灵的数量,然后跑最小生成树,这样时间复杂度将是O(max(ai)M),不能通过全部数据。想一想,每一次跑最小生成树的时候,都是拆开来重建一遍,这样就浪费了之前构建好的信息。要充分利用之前的信息,可以使用以下的方法:
    首先对于所有边按ai从小到大排序,然后逐个添加边,每添加一条边之前,检查边的两个端点是否已经连通,如果没有连通就直接连接这一条边,否则,询问已经构建好的树中的这两个端点之间的路径上的最大bi,如果这个最大值要比要添加的边的bi大,那么就去掉原来的边而把新边加入,否则不动。添加完边之后,检查点1和点N是否连通,如果连通,那么就用这两个点之间路径上最大的bi加上刚添加的边的ai来更新答案即可。显然,当要添加的边的ai已经比已知答案更大,就直接退出,据悉,这个剪枝能省不少时间。
    以上的方法是可实现的,关键在于三个点:一是判断两点之间的连通性,二是边的删除和插入,三是询问路径上的边权最大值。那么我们就可以用LCT来维护了,因为LCT直接维护边权十分麻烦,所以我们可以把边化为点,点权为原来的边权,连接原来的两个端点,原来的点的点权均为0,这样就可以得到一个等价的图。以上算法的复杂度为O(Mlog(N+M)),可以通过全部数据。
    犯二的地方:多了去了,太久不写这种代码量大的题了,尤其是LCT,实现的诸多细节都忘了,调了好久……还有就是翻转操作的逻辑顺序,一定要先把触及到的点的孩子节点互换,否则会引发玄学错误,从而导致从100分到10分的飞降(这时候就不能用“飞跃”了是不是)。
    以下是本人代码:

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #define inf 1000000000
    using namespace std;
    int n,m,ans=inf;
    int pre[150010]={0},ch[150010][2]={0},val[150010]={0},mx[150010]={0},mp[150010];
    struct edge {int x,y,a,b;} e[100010];
    bool rev[150010]={0},rt[150010];
    
    bool cmp(edge a,edge b) {return a.a<b.a;}
    
    void reverse(int x)
    {
      rev[x]^=1;
      swap(ch[x][0],ch[x][1]);
    }
    
    void pushdown(int x)
    {
      if (rev[x])
      {
        rev[x]=0;
        reverse(ch[x][0]);
        reverse(ch[x][1]);
      }
    }
    
    void pushup(int x)
    {
      if (val[x]>mx[ch[x][0]]&&val[x]>mx[ch[x][1]]) mx[x]=val[x],mp[x]=x;
      else if (mx[ch[x][0]]>mx[ch[x][1]]) mx[x]=mx[ch[x][0]],mp[x]=mp[ch[x][0]];
           else mx[x]=mx[ch[x][1]],mp[x]=mp[ch[x][1]];
    }
    
    void rotate(int x,bool f)
    {
      int y=pre[x];
      ch[y][!f]=ch[x][f];
      pre[ch[x][f]]=y;
      ch[x][f]=y;
      if (rt[y]) rt[y]=0,rt[x]=1;
      else ch[pre[y]][ch[pre[y]][1]==y]=x;
      pre[x]=pre[y];
      pre[y]=x;
      pushup(y),pushup(x);
    }
    
    void Push(int x)
    {
      if (!rt[x]) Push(pre[x]);
      pushdown(x);
    }
    
    void Splay(int x)
    {
      Push(x);
      while(!rt[x])
      {
        if (rt[pre[x]]) rotate(x,ch[pre[x]][0]==x);
        else
        {
          int y=pre[x],z=pre[pre[x]];
          bool f=(ch[z][1]==y);
          if (ch[y][f]==x) rotate(y,!f),rotate(x,!f);
          else rotate(x,f),rotate(x,!f);
        }
      }
      pushup(x);
    }
    
    void access(int x)
    {
      int y=0;
      do
      {
        Splay(x);
        rt[ch[x][1]]=1,rt[ch[x][1]=y]=0;
        pushup(x);
        x=pre[y=x];
      }while(x);
    }
    
    void move(int x)
    {
      access(x);
      Splay(x);
      reverse(x);
    }
    
    void link(int ed,int a,int b)
    {
      move(a);
      move(b);
      pre[a]=pre[b]=ed;
    }
    
    void cut(int ed,int a,int b)
    {
      move(ed);
      access(ed);
      Splay(a),Splay(b);
      pre[a]=pre[b]=0;
    }
    
    int find_root(int x)
    {
      int now=x;
      while(ch[now][0]) now=ch[now][0];
      return now;
    }
    
    bool check(int x,int y)
    {
      int rx,ry;
      access(x);Splay(x);rx=find_root(x);
      access(y);Splay(y);ry=find_root(y);
      return rx==ry;
    }
    
    void query(int x,int y,int &mxd,int &mxi)
    {
      move(x);
      access(y);
      Splay(y);
      mxd=mx[y];
      mxi=mp[y];
    }
    
    void work()
    {
      sort(e+1,e+m+1,cmp);
      for(int i=1;i<=n+m;i++)
      {
        rt[i]=1;mx[i]=val[i]=0;mp[i]=i;
        if (i>n) mx[i]=val[i]=e[i-n].b;
      }
    
      for(int i=1;i<=m;i++)
      {
        int mxd,mxi;
        if (check(e[i].x,e[i].y))
        {
          query(e[i].x,e[i].y,mxd,mxi);
          if (mxd>e[i].b)
          {
            cut(mxi,e[mxi-n].x,e[mxi-n].y);
            link(n+i,e[i].x,e[i].y);
          }
        }
        else link(n+i,e[i].x,e[i].y);
        if (check(1,n))
        {
          query(1,n,mxd,mxi);
          ans=min(ans,e[i].a+mxd);
        }
        if (e[i].a>ans) break;
      }
    }
    
    int main()
    {
      scanf("%d%d",&n,&m);
      for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].a,&e[i].b);
    
      work();
    
      if (ans==inf) printf("-1");
      else printf("%d",ans);
      return 0;
    }
    
  • 相关阅读:
    从今天开始,逐步的把EverNote摘抄的笔记发送到这里来,也算是写博客的一个开始吧
    驾校一点通变身软考题库
    从公司去西站时间估算
    Android应用程序与JS双向调用
    操作系统为fedora17,安装Qt4.7.3,编译时找不到libpng12.so.0
    symbian 静默安装与静默删除(转)
    Symbian 利用Socket发送短信的源码(转)
    QT在vs2005平台下的安装与调试(转)
    c++中break的用法、continue的用法
    symbian入门系列 Windows C++ 程序员如何过度到Symbian C++ ?(转)
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793688.html
Copyright © 2020-2023  润新知