bzoj 2049: [Sdoi2008]Cave 洞穴勘测 (时间分治+带撤销并查集)

可加边删边,询问两个点连通性的一个题。


好像可以lct。


但是跟着金桔学了时间分治+带撤销并查集的做法,第一次写时间分治。


时间分治的话就是以操作序号为时间,对应对一颗线段树上去,然后在线段树上分治找到操作时间对应的节点,进行操作。

对于一条边,它存活的时间就是【加边的时间,删边的时间】这样一个区间,然后我们这条边push到线段树上去,分散的存在各个节点,注意不能push_up,这个过程其实就是一个不push_up也不push_down的线段树区间更新,而push_up不进行的原因就是线段树节点只能存放在该节点对应整个时间内存在的边,就是把一段时间分散到不同的时间段里去了,不push_down的原因是让防止边重复出现,也没有必要。这样的话由于不会一直跑到线段树底部,最多是log(4*m)的复杂度。所以这个插入操作的时间复杂度最多是m*log(4*m)。


然后就是对查询进行处理了。

这时候需要对线段树dfs一下,递归到底层,也就是区间就是一个点x的时候,从这个点一直到根节点所经历的所有区间的边,都是在这个时间x存在的边了,因为x都且只包含在这些区间里,注意区间对应时间。所以假如我们根节点到当前节点存储的所有的边都用并查集连接的话,那么当前时间的询问(如果是询问的话)边是否连通就很好做了。这个时候就需要一个带撤销的并查集了,并查集需要是按秩合并的并查集,路径压缩的并查集我么是没有办法分裂的,这样的话不可撤销,如果没有优化的并查集的话,就可能超时,按秩合并即可以分裂,有保证了logn的复杂度,撤销的具体操作就看代码吧。


然后我们dfs的时候,进入一个节点就先将这个时间存在的边都先merg,每次退出节点前再讲原来的merg撤销,这样就做到了每次到叶子节点都维护了一个从根节点到当前节点对应的边所组成的一个并查集。然后就做完了。


第一次蜜汁数组开成maxn而不是maxm,re了一发,不然就一A了QAQ。


代码:

#include 
#define ps push_back
using namespace std;
const int maxn=1e4+5;
const int maxm=2e5+5;
int fa[maxn];
int rk[maxn];
stack >sta;
void init(int n)
{
    int i;
    for(i=0; i<=n; i++)
    {
        fa[i]=i;
        rk[i]=0;
    }
    return;
}
int find(int x)
{
    if(x==fa[x])return x;
    else return find(fa[x]);
}

int  merg(int x, int y)
{
    x=find(x), y=find(y);
    if(x==y)return 0;
    if(rk[x]<=rk[y])
    {
        sta.push(make_pair(&fa[x], fa[x]));
        fa[x]=y;
        if(rk[x]==rk[y])
        {
            sta.push(make_pair(&rk[y], rk[y]));   
            rk[y]++;
            return 2;
        }
    }
    else 
    {
        sta.push(make_pair(&fa[y], fa[y]));
        fa[y]=x;
    }

    return 1;
}

void back()
{
    *sta.top().first=sta.top().second;
    sta.pop();
    return;
}

#define lson o<<1 
#define rson o<<1|1
#define MID int mid=(l+r)>>1
vector >val[maxm<<2];
int tim[maxm<<2];
bool query[maxm];
bool ans[maxm];
int  fr[maxm];
int to[maxm];

void insert(int o, int l, int r, int ll, int rr, pair pa)
{
    if(ll<=l && r<=rr)
    {
        val[o].ps(pa);
        return;
    }

    MID;
    if(ll<=mid)insert(lson, l, mid, ll, rr, pa);
    if(rr>mid)insert(rson, mid+1, r, ll, rr, pa);
    

    return;
}

void dfs(int o, int l, int r)
{
    tim[o]=0;
    for(int i=0; i<(int)val[o].size(); i++)
    {
        tim[o]+=merg(val[o][i].first, val[o][i].second);
//        printf("%d %d %d %d\n", l ,r, val[o][i].first, val[o][i].second);
    }
    if(l==r)
    {
        
        if(query[l])
        {
//            printf("%d %d %d %d\n", l, tim[o], find(fr[l]), find(to[l]));
            ans[l]=(find(fr[l])==find(to[l]));
        }
    }
    else 
    {
    
    MID;
    
    dfs(lson, l, mid);
    dfs(rson, mid+1, r); 
    }

    while(tim[o]--)
    {
        back();
    }




    
    return;
}



map, int>mp;
map, int>::iterator it;

int main()
{
    int i, j, n, m;
    cin>>n>>m;
    pairpai;
    char op[20];
    for(i=1; i<=m; i++)
    {
        scanf("%s%d%d", op, &fr[i], &to[i]);
        if(fr[i]>to[i])swap(fr[i], to[i]);
        pai.first=fr[i], pai.second=to[i];
        if(op[0]=='Q')
        {
            query[i]=true; 
        }
        else if(op[0]=='C')
        {
            mp[pai]=i;
        }
        else 
        { 
            insert(1, 1, m, mp[pai], i, pai);
            mp.erase(pai);
        }
    }
    
    for(it=mp.begin(); it!=mp.end(); it++)
    {
        insert(1, 1, m, it->second, m, it->first);
    }
    init(n);

    dfs(1, 1, m);
    for(i=1; i<=m; i++)
    {
        if(query[i])
        {
            printf(ans[i]?"Yes\n":"No\n");
        }
    }
    return 0;



}


你可能感兴趣的:(acm,studyproblem,时间分治,数据结构,并查集,线段树)