【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;
}
原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793688.html