【雅礼联考GDOI2017模拟9.2】Ztxz16学图论

11 篇文章 0 订阅
7 篇文章 0 订阅

Description

众所周知,Zjr506是算法之神,因此Ztxz16经常向他请教算法。这一天,Zjr506在教导了Ztxz16关于图论方面的一些算法后,给他出了一道图论题作为家庭作业:
给定N个点,M条无向边,Q个询问,每个询问给定L, R,问连上第L~R条边后,图中有多少联通块(询问之间互不影响)。
Ztxz16智商太低,百思不得其解,只好向你请教这个问题。

Solution

用LCT做的

把模型转化一下。第i条边的权值是i,每次不断的加边,维护一个最大生成树。可以用LCT来维护删边和加边,最后树的个数就是联通块的数量。为什么是最大生成树,因为这样更方便删边和加边。
那么我们可以处理出ans数组,ans[i]表述1~i的边都添加后的答案。
同时我们还要维护n棵主席树为权值线段树,一条边若对ans[i]有贡献,则在第i棵主席树的值为1。
那么对于一个询问l和r,则答案为ans[r]+第r棵主席树在区间1~l-1的权值和。

莫队算法

如果只有加边操作的话,会很好做。那么我们就把所有的删边操作变成加边操作。
把[ll,rr]的询问,l先到l所在块的最右端,然后再添加[ll,l],做完之后再还原,用data[i]表示i原来的父亲,用于还原。
注意,r是逐个递增的,当l的块变动的时候,就暴力重构。
每次操作之后上一层的f都要还原。r的变动时,要修改data。
细节比较多,防止出错,把l,r同块的情况分出来了。

Code

#include<iostream> 
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200007;
int i,j,k,l,t,n,m,ans;
int kuai[maxn],data[maxn],head,tail,da,r,num;
int b[maxn],c[maxn],ans1[maxn],f[maxn],ans2,ans3;
int bz[maxn],bb;
struct node{
    int a,b,c;
}a[maxn],d[maxn];
bool cmp(node x,node y){
    return kuai[x.a]<kuai[y.a]||kuai[x.a]==kuai[y.a]&&x.b<y.b;
}
bool cmp1(node x,node y){
    return x.a<y.a||x.a==y.a&&x.b<y.b;
}
int gf(int x){
    if(bz[x]!=bb){
        bz[x]=bb;
        f[x]=data[x];
    }
    if(f[x]==x)return x;
     f[x]=gf(f[x]);
    return f[x];
}
int gf1(int x){
    if(bz[x]!=bb){
        bz[x]=bb;
        f[x]=data[x];
    }
    if(f[x]==x)return x;
    data[x]=f[x]=gf(f[x]);
    return f[x];
}
void jin(int x){
    int u=gf(b[x]),v=gf(c[x]);
    if(u!=v)ans--,f[v]=u;
}
void jin1(int x){
    int u=gf1(b[x]),v=gf1(c[x]);
    if(u!=v)ans--,f[v]=data[v]=u;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    da=100;
    fo(i,1,n)kuai[i]=(i-1)/da+1;
    fo(i,1,m){
        scanf("%d%d",&b[i],&c[i]);
    }
    fo(i,1,k){
        scanf("%d%d",&a[i].a,&a[i].b);
        a[i].c=i;
    }
    sort(a+1,a+1+k,cmp);
    l=0;
    ans=n;
    fo(i,1,n)f[i]=i;
    fo(j,1,k){
        if(a[j].b<kuai[a[j].a]*da){
            d[++num]=a[j];
            continue;
        }
        if(kuai[l]!=kuai[a[j].a]){
            fo(i,1,n){
                f[i]=i,bz[i]=bb;
            }
            ans=n;
            l=kuai[a[j].a]*da,r=a[j].b;
            fo(i,l,r)jin(i);
            fo(i,1,n)data[i]=f[i];
            ans2=ans;
        }
        bb++;l=kuai[a[j].a]*da;ans=ans2;
        while(r<a[j].b)jin1(++r);ans2=ans;
        while(l>a[j].a)jin(--l);
        ans1[a[j].c]=ans;
    }
    sort(d+1,d+1+num,cmp1);
    l=r=0;ans=n;
    fo(i,1,n)data[i]=i;
     fo(j,1,num){
        if(l<d[j].a){
            bb++,ans=n;
            fo(i,d[j].a,d[j].b)jin(i);
            l=d[j].a,r=d[j].b;
        }
        while(r<d[j].b)jin(++r);
        ans1[d[j].c]=ans;
    }
    fo(i,1,k)printf("%d\n",ans1[i]);
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值