计蒜客 ICPC南京网络赛 Set(字典树 + 合并 + lazy更新)

版权声明:本文为博主原创文章,转载请著名出处 http://blog.csdn.net/u013534123 https://blog.csdn.net/u013534123/article/details/82470132

大致题意:n个集合,你要进行m个操作。总共有3种操作。第一种,合并两个集合x和y。第二张,把特定的集合里面所有的数字加一。第三种,询问在某个集合里面,对于所有数字对2的k次方取模后,有多少个数字等于x。

首先,合并的话实在是有太多的方式,大部分数据结构的启发式合并包括set都可以。但是主要是第三个,所有数字对2的k次方取模,然后看结果有多少个数字等于x。仔细想想这个取模,不难发现,其实就是询问,一个集合里面,在二进制下,后k位x的数字有多少个。询问与二进制和位数有关,很容易想到用字典树。

字典树的合并比较容易写,当一棵树有某个儿子,而另一个没有的时候,直接把没有的儿子指向有的,其余情况暴力合并即可。问题的关键在于这个集合整体加一怎么处理。我们考虑加一对于一个数字来说会有什么样的影响。由于我实在二进制下存储,那么我考虑影响当然也是在二进制下的。对于某一位,如果为0,那么加一之后会变成一,对其它位不产生影响;如果为1,那么加一之后会变成零,同时对下一位产生进位。因此加一操作,对于某个节点的两个儿子来说,相当于是交换了左右儿子,然后对于原本的1这个儿子,还对下一位产生了额外贡献。进一步总结我们可以发现,如果对某个点加x,这个x如果位奇数,那么和加一的情况类似;如果为偶数,那么就不会交换两个儿子,但是要对两个儿子产生x/2的进位。

如此,我们就大致有了一个处理的方法。我们可以对于每一个节点保存一个lazy标记,这个标记类似于线段树中的lazy标记。每次整个集合增加的时候,只改变lazy标记,然后在下一次访问这个节点的时候,再去把这个标记push_down。而这个push_down的方式就是按照之前说的那样,根据lazy的奇偶来判断是否应该交换儿子和额外进位。对于每一个查询操作,我们直接把放到字典树中,确定一个位置,输出对应节点的size即可。具体操作的时候还要注意,一定要把每一个插入的数字固定插入长度设置为30,因为数字的高位即使为0也是需要保存的。

最后就是复杂度分析。一次插入操作的代价是O(30)≈O(log1e9),然后对于每次的增加操作,其实即使是暴力,它的复杂度也才O(30),因为交换左右儿子之后,需要改变的只有原本为1的这个儿子,因此最多往下走深度这么多个节点。最后就是字典树的合并,考虑初始的时候,每个树中有一个数字,节点数目是O(30),然后一次合并操作是log的复杂度,合并之后会少一棵树,合并复杂度就是O(nlog1e9)。询问当然也是一个询问O(30)的复杂度。因此,最后总的时间复杂度就是O((n+q)log1e9)。具体见代码:

#include<bits/stdc++.h>
#define mod 1000000007
#define LL long long
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;

const int N = 6e5 + 10;
const int depth = 31;

struct Trie
{
    #define ls T[x].ch[0]
    #define rs T[x].ch[1]
    void init(){tot=0;}
    struct node{int size,ch[2],lazy;}T[N<<5];int tot;
    int newnode(){memset(&T[++tot],0,sizeof(T[0]));return tot;}

    void push_down(int x)
    {
        int lz=T[x].lazy;
        if (lz&1) {swap(ls,rs);T[ls].lazy++;}
        T[ls].lazy+=lz/2; T[rs].lazy+=lz/2;
        T[x].lazy=0;
    }

    void ins(int &rt,int x)
    {
        int o=rt?rt:rt=newnode(),c;
        for(int k=0;k<depth;k++)
        {
            c=x&1; x>>=1; T[o].size++;
            if (T[o].lazy) push_down(o);
            if (!T[o].ch[c]) T[o].ch[c]=newnode();
            o=T[o].ch[c];
        }
    }

    int query(int rt,int x,int y)
    {
        int o=rt;
        for(int k=0;k<y;k++)
        {
            if (T[o].lazy) push_down(o);
            o=T[o].ch[x&1]; x>>=1; if (!o) break;
        }
        return T[o].size;
    }

    void Merge(int x,int y)
    {
        T[x].size+=T[y].size;
        if (T[x].lazy) push_down(x);
        if (T[y].lazy) push_down(y);
        for(int i=0;i<2;i++)
        {
            if (T[x].ch[i]&&T[y].ch[i]) Merge(T[x].ch[i],T[y].ch[i]);
            if (!T[x].ch[i]&&T[y].ch[i]) T[x].ch[i]=T[y].ch[i];
        }
    }

} Trie;

int n,m,rt[N],f[N];

int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}

int main()
{
    while(~sf(n))
    {
        sf(m);
        clr(rt,n);
        Trie.init();
        for(int i=1;i<=n;i++)
        {
            f[i]=i;
            int x; sf(x);
            Trie.ins(rt[i],x);
        }
        while(m--)
        {
            int op; sf(op);
            if (op==1)
            {
                int x,y;
                sf(x); sf(y);
                x=find(x); y=find(y);
                if (x!=y)
                {
                    Trie.Merge(rt[x],rt[y]); f[y]=x;
                }
            }
            if (op==2)
            {
                int x; sf(x);
                Trie.T[rt[find(x)]].lazy++;
            }
            if (op==3)
            {
                int x,y,z; sc(x,y,z); x=find(x);
                printf("%d\n",Trie.query(rt[x],z,y));
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/82470132