【JZOJ3663】【SHTSC2014】神奇化合物(compound) (最小生成树+并查集)

Problem

这里写图片描述

Input

这里写图片描述

Output

  对于每个Q操作,输出一行个整数为相应时刻的分子。

Hint

这里写图片描述

Solution

  一眼看过去,不就是弦图吗没思路。再看一下数据范围,顿时感觉有点希望。
  由于我不会弦图,所以我在这里提供一种题目不需保证给出的图是弦图也能过的方法。
  首先,考虑对于联通块a和b来说,若有x,y两边将它们连接,且x比y早报销,不考虑联通块以内的情况,x是没有建的必要的。
  所以我们将会报销的第i条边的权值设定为 qti q − t i ,q为操作数, ti t i 为第i条边报销的时间。不直接设为 ti − t i 是因为如果它不会报销,它就不会被设定权值,权值即为0;如果会报销,权值则会≥0。
  我们首先将初始的m条边排个序,然后做一遍kruskal,加上并查集优化。设此时最小生成树中的k条边为 a1,a2,...,ak a 1 , a 2 , . . . , a k
  然后对于连边操作,我们就把新的那条边插入排序到a数组中,然后再做一遍kruskal。
  对于删边操作,我们把删的那条边在最小生成树中找一遍,如果找到了,我们就删掉它,然后再做一遍kruskal。
  对于询问,我们可以扫一遍所有点,算出它们在在并查集中的getfat,记录下不同的getfat个数,即为答案。
  但是还可以优化。
  对于连边操作,如果因此而多选了一条边,那么联通块的个数肯定–;对于删边操作,如果在最小生成树中删掉它了,那么联通块的个数肯定++。
  所以,我们只需要一开始先求出一个cnt,每次操作就–、++或不变,这样就可以达成询问 O(1) O ( 1 )
  时间复杂度: O(mlog2m+nqα(n)) O ( m l o g 2 m + n q α ( n ) )
  补充一句:这样做很诡异地慢,所以请无视掉下发代码中的O3

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define M 200001
#define Q 10001
#define S M+Q
#define N 5001
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
int i,j,l,n,m,q,x[Q],y[Q],s,map[N][N],t,k,fat[N],cnt,bz[N],next[S],last[N][N],lk;
struct side
{
    int a,b,t,num;
}a[M],b[Q];
char ch[Q];
__attribute__((optimize("-O3")))
inline bool operator<(const side&a,const side&b)
{
    return a.t<b.t;
}
__attribute__((optimize("-O3")))
int gef(int x)
{
    return x==fat[x]?x:fat[x]=gef(fat[x]);
}
__attribute__((optimize("-O3")))
void kruskal(int m)
{
    int i,fx,fy;
    k=0;
    fo(i,1,n)fat[i]=i;
    fo(i,1,m)
    {
        fx=gef(a[i].a);
        fy=gef(a[i].b);
        if(fx!=fy)
        {
            fat[fx]=fy;
            a[++k]=a[i];
            if(k==n-1)break;
        }
    }
}
__attribute__((optimize("-O3")))
int main()
{
    freopen("compound.in","r",stdin);
    freopen("compound.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m)
    {
        scanf("%d%d",&a[i].a,&a[i].b);
        if(a[i].a>a[i].b)swap(a[i].a,a[i].b);
        last[a[i].a][a[i].b]=map[a[i].a][a[i].b]=i;
        a[i].num=i;
    }
    scanf("%d",&q);
    fo(i,1,q)
    {
        do
            scanf("%c",&ch[i]);
        while(ch[i]=='\n');
        if(ch[i]=='Q')continue;
        scanf("%d%d",&x[i],&y[i]);
        if(x[i]>y[i])swap(x[i],y[i]);
        if(ch[i]=='A')
        {
            s++;
            if(!map[x[i]][y[i]])
                    map[x[i]][y[i]]=s+m;
            else    next[map[x[i]][y[i]]]=s+m;
            last[x[i]][y[i]]=s+m;
            b[s].a=x[i];
            b[s].b=y[i];
            b[s].num=s+m;
            continue;
        }
        t=last[x[i]][y[i]];
        if(t<=m)
                a[t].t=q-i;
        else    b[t-m].t=q-i;
    }
    sort(a+1,a+m+1);
    kruskal(m);
    fo(j,1,n)
        if(bz[gef(j)]<i)
            cnt++,bz[fat[j]]=i;
    fo(i,1,q)
        switch(ch[i])
        {
            case 'A':
            {
                t=map[x[i]][y[i]];
                a[k+1]=b[t-m];
                fd(j,k,1)
                    if(a[j+1].t<a[j].t)
                            swap(a[j+1],a[j]);
                    else    break;
                lk=k;
                kruskal(k+1);
                cnt+=lk-k;
                break;
            }   
            case 'D':
            {
                t=map[x[i]][y[i]];
                fo(j,1,k)
                    if(a[j].num==t)
                    {
                        fo(l,j,k)a[l]=a[l+1];
                        kruskal(--k);
                        cnt++;
                        break;
                    }
                map[x[i]][y[i]]=next[t];
                break;
            }
            case 'Q':
            {
                printf("%d\n",cnt);
                break;
            }
        }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值