【离线+并查集按秩合并】UOJ14(UER #1)[DZY Loves Graph]题解

6 篇文章 0 订阅
3 篇文章 0 订阅

题目概述

给出 n 个点 m 个操作,操作如下:

  1. 加入 a b 长度为 i i 是该操作的标号)的边。
  2. 删除边权前 k 大的边。
  3. 返回到 i2 状态。

求每次操作后最小生成树的权值。

解题报告

刚开始看成第k大的边,然后……

回退?可持久化啊!其实这道题用离线会非常简单……我们先考虑没有返回操作的情况,由于边权从小到大,所以每次删除就是删除最近的 k 条边,又因为每条边只会被删除一次,所以用按秩合并的并查集就可以在 O(mlog2n) 的时间内解决。

但是有返回操作,我们离线,直接检查 i 操作后面是不是返回操作,如果是的话(不是就直接处理):

  1. i 操作是加入:先加入,输出答案,然后回退。

    • i 操作是删除:预处理出用前 k 小边的最小生成树的权值 ans[k] ,输出答案,不执行删除操作。
    • 示例程序

      #include<cstdio>
      using namespace std;
      typedef long long LL;
      const int maxn=300000,maxm=500000;
      
      int n,m,a[maxm+5],b[maxm+5];char td[maxm+5];
      int E,fa[maxn+5],rk[maxn+5];
      struct data {int a,b,fa,ra,fb,rb;};
      data lst[maxm+5];LL ans[maxm+5];int num[maxm+5];
      
      #define Eoln(x) ((x)==10||(x)==13||(x)==EOF)
      inline char readc()
      {
          static char buf[100000],*l=buf,*r=buf;
          if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
          if (l==r) return EOF;return *l++;
      }
      inline int readi(int &x)
      {
          int tot=0,f=1;char ch=readc(),lst='+';
          while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}
          if (lst=='-') f=-f;
          while ('0'<=ch&&ch<='9') tot=(tot<<3)+(tot<<1)+ch-48,ch=readc();
          return x=tot*f,Eoln(ch);
      }
      inline void writel(LL x)
      {
          static int buf[20],len;len=0;do buf[len++]=x%10,x/=10; while (x);
          while (len) putchar(buf[--len]+48);putchar('\n');
      }
      inline char getrch() {char ch=readc();while ('Z'<ch||ch<'A') ch=readc();return ch;}
      int getfa(int x) {if (fa[x]==x) return x;return getfa(fa[x]);}
      inline void Back(int E)
      {
          fa[lst[E].a]=lst[E].fa;rk[lst[E].a]=lst[E].ra;
          fa[lst[E].b]=lst[E].fb;rk[lst[E].b]=lst[E].rb;
      }
      inline void Add(int i)
      {
          int fx=getfa(a[i]),fy=getfa(b[i]);
          ans[E+1]=ans[E];num[E+1]=num[E];
          lst[++E]=(data){fx,fy,fa[fx],rk[fx],fa[fy],rk[fy]};
          if (fx==fy) writel(num[E]<n-1?0:ans[E]); else
          {
              ans[E]+=i;num[E]++;
              if (rk[fx]<rk[fy]) fa[fx]=fy; else
              if (rk[fx]>rk[fy]) fa[fy]=fx; else
              rk[fy]++,fa[fx]=fy;writel(num[E]<n-1?0:ans[E]);
          }
          if (i+1<=m&&td[i+1]=='R') Back(E--),writel(num[E]<n-1?0:ans[E]);
      }
      inline void Delete(int i)
      {
          int pos=E-a[i];writel(num[pos]<n-1?0:ans[pos]);
          if (i+1<=m&&td[i+1]=='R') return writel(num[E]<n-1?0:ans[E]);
          while (E>pos) Back(E--);
      }
      int main()
      {
          freopen("program.in","r",stdin);
          freopen("program.out","w",stdout);
          readi(n);readi(m);
          for (int i=1;i<=m;i++)
              switch(td[i]=getrch())
              {
                  case 'A':readi(a[i]),readi(b[i]);break;
                  case 'D':readi(a[i]);break;
              }
          for (int i=1;i<=n;i++) fa[i]=i;
          for (int i=1;i<=m;i++)
              if (td[i]=='A') Add(i); else if (td[i]=='D') Delete(i);
          return 0;
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值