bzoj 2049: [Sdoi2008]Cave 洞穴勘测


题目链接:

http://www.lydsy.com/JudgeOnline/problem.php?id=2049

他们都说是LCT的板子题,然而并不会LCT。

这里纯粹是来练时间分治的。

主要是学习了一下时间分治加可撤销的并查集。然后加深了对深搜递归的应用的理解。回溯撤销。

我们把操作全部记录下来,把每条边的存活区间记录下来。对于一个时间点的查询,我们把当前存活的边连接起来看一下是否能够联通就可以了。

具体的可以看一下这个:

http://blog.csdn.net/johsnows/article/details/73322093

时间分治的话就是以操作序号为时间,对应对一颗线段树上去,然后在线段树上分治找到操作时间对应的节点,进行操作。
对于一条边,它存活的时间就是【加边的时间,删边的时间】这样一个区间,然后我们这条边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撤销,这样就做到了每次到叶子节点都维护了一个从根节点到当前节点对应的边所组成的一个并查集。然后就做完了。


然后是代码:

#include <cstdio>
#include <stack>
#include <map>
#include <vector>

using namespace std;
int n,m;
const int MAXN = 1e4+7;
const int MAXM = 2e5+7;

int pre[MAXN],d[MAXN];
stack<pair<int*,int> >S;
void init()
{
    for(int i = 1 ; i <= n ; ++i)
    {
        pre[i] = i;
        d[i] = 0;
    }
}
int findx(int x)
{
    return pre[x] == x? x : findx(pre[x]);
}
int he(int x,int y)
{
    x = findx(x);
    y = findx(y);
    if(d[x] <= d[y])
    {
        S.push(make_pair(&pre[x],pre[x]));
        pre[x] = y;
        if(d[x] == d[y])
        {
            S.push(make_pair(&d[y],d[y]));
            d[y]++;
            return 2;
        }
    }
    else
    {
        S.push(make_pair(&pre[y],pre[y]));
        pre[y] = x;
    }
    return 1;
}
void b_back()
{
    *S.top().first = S.top().second;
    S.pop();
}
vector<pair<int,int> >val[MAXM<<2];
int tim[MAXM<<2];
bool q[MAXM];
bool ans[MAXM];
int u[MAXM];
int v[MAXM];
void cha(int i,int l,int r,int l1,int r1,pair<int,int>link)
{
    if(l == l1 && r == r1)
    {
        val[i].push_back(link);
        return ;
    }
    int mid = (l+r)>>1;
    if(r1 <= mid)cha(i<<1,l,mid,l1,r1,link);
    else if(l1 > mid)cha(i<<1|1,mid+1,r,l1,r1,link);
    else cha(i<<1,l,mid,l1,mid,link),cha(i<<1|1,mid+1,r,mid+1,r1,link);
}
void dfs(int i,int l,int r)
{
    tim[i] = 0;
    for(int j = 0,l = val[i].size() ; j < l ; ++j)
    {
        tim[i] += he(val[i][j].first,val[i][j].second);
    }
    if(l == r)
    {
        if(q[l])
        {
            ans[l] = (findx(u[l]) == findx(v[l]));
        }
    }
    else
    {
        int mid = (l+r)>>1;
        dfs(i<<1,l,mid);
        dfs(i<<1|1,mid+1,r);
    }
    while(tim[i]--)b_back();
}
map<pair<int,int>,int>side;
map<pair<int,int>,int>::iterator it;
int main()
{
    char s[10];
    scanf("%d%d",&n,&m);
    for(int i = 1 ; i <= m ; ++i)
    {
        scanf("%s%d%d",s,&u[i],&v[i]);
        if(u[i] > v[i])swap(u[i],v[i]);
        pair<int,int>link = make_pair(u[i],v[i]);
        if(s[0] == 'Q')q[i] = 1;
        else if(s[0] == 'C')
        {
            side[link] = i;
        }
        else
        {
            cha(1,1,m,side[link],i,link);
            side.erase(link);
        }
    }
    for(it = side.begin() ; it != side.end() ; it++)
    {
        cha(1,1,m,it->second,m,it->first);
    }
    init();
    dfs(1,1,m);
    for(int i = 1 ; i <= m ; ++i)if(q[i])printf(ans[i]?"Yes\n":"No\n");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值