JZOJ5822 【NOIP提高A组模拟2018.8.16】 量子纠缠

这是一道很巧妙的题目。
今早,我调了好久,终于将它切掉了……


题目

Description

Description

Input

第一行包含一个正整数 m,代表操作数。
接下来 m 行,每行可能有以下形式:
1 s 代表将数字串 s 加入信息集中
2 s 代表询问数字串 s 是否在信息集中
3 a b 代表使数字串 a 和 b 互相纠缠

Output

对于每一个 2 操作,如果询问串不在集合中,请输出一行一个整数 0,否则输出一行一个整 数 1。

Sample Input

11
1 123
2 123
2 0
3 12 13
1 124
2 133
2 134
2 13
3 1 11
2 111
2 11111111111111111111111124

Sample Output

1
0
1
1
0
0
1

Data constraint

Data constraint

题目大意是这样的:
维护一个数字串集合,每一次有三个操作:
1. 插入一个数字串。
2. 询问一个数字串是否在集合中。
3. 纠缠两个数字串(不一定在这些数字串之内),假设这两个数字串分别为AB,插入一些数字串,使得对于任意集合中的A+C,都有B+C也在集合中;对于任意集合中的B+D,都有A+D在集合中(有可能纠缠过后会加入无限的数字串)。

解题思路

我才不会告诉你,我一开始看到这题时,没有理解题目大意。我以为这些加入集合的字符串全部合并在了一起,一想到可能要用某些高端的字符串数据结构,我就感到害怕,于是果断地弃掉了这题。
显然,如果只有前两个操作,那么这就是一道裸裸的Trie。
为什么?不知道为什么的同志们,请再次仔细地理解一下题目大意。如果还不知道为什么,就查查Trie到底是个什么东西。说真的,我觉得Trie是一种无师自通的玩意儿。只要你知道Trie是个什么东西,那么前两个操作对于你来说一定不难。
现在的问题是如何搞第三个操作。
既然前两个操作都和Trie建立了联系,那么不妨想想,如何用Trie来解决。
我们在看看所谓“纠缠”的条件。什么是“纠缠”?把Trie上表示AB点找出来,设为ab
那么我们是不是可以理解成,ab为根的子树,长得一模一样?
可能有些不好想象,感受一下~~~
算了,放张图。

同化
这样可以理解了吗?
就是让以它们为根的子树长得一模一样。
所以有个思路就是,先把ab找出来,暴力地同化它们的子树。
诶~等等,似乎会超时。
读者:你在逗我?
暴力同化它们的子树,这是不现实的。不仅暴力同化花很多时间空间,而且在以后,当有节点插入时,还要在搞,比蜗牛还慢……
与其这样,不如合并ab。这样子,下面的就变得一模一样。当后面有插入时,那也可以直接修改,反正两个已经合为一体了。
这个方法非常神奇。显然,它也是正确的。
可能你们会有一堆问题吧,比如,如何合并?
合并
先将ab找出来,然后合并。再递归它们的儿子,分别合并。(用并查集)
这样的时间复杂度是不会炸的。建Trie所需要的节点是有限的。在没合并之前,它们就是互相独立的块。每次的合并,将会减少一个块。下次再访问它们时,它们已经是一体的了。
无限的情况怎么办?
其实无限的情况是没必要特判的。相信聪明的读者已经发现了,实际上,出现无限的情况,也就是在合并的时候一个节点和它的祖先合并。那么这些Trie上的转移边,实际上就变成了一个有向图。查询的时候,就像以前那样查询就行了。
一棵好好的Trie树,经过各种合并后,形成了一个有向图……是不是很神奇呢?

算法流程

前面的两个操作就不说了,直接将第三个操作的核心——合并。
假设现在要合并以ab为根的子树。
首先,将ab各自跳到getfather
如果a=b则退出。
a所在的集合,并入b所在的集合中。用a尝试更新b上的have。(表示这个节点是否有字符串)
然后,枚举它们的儿子。
1. 如果ab都没有儿子,不理它们。
2. 如果a有儿子,而b没有儿子,那么,将b的这个转移边连过去。
3. 如果a没有儿子,b有儿子,不理它们(因为是用a合并到b中)。
4. 如果两个都有儿子,那么递归合并它们的儿子,合并完后,b跳到它的getfather(这一条很玄学,某大佬的解释是:有可能合并完它们的儿子之后,会对已经和b同一个并查集中的节点有影响,因为在各种合并之后,这不再是一棵树,而是有向图)。

代码

千言万语不如一标程。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
struct Trie
{
    int c[10];//转移边
    bool have;//表示这点表示的字符串是否存在于集合之中
    int n;//这个节点所在的并查集编号
    int num(); //num()就相当于平常打的getfather。在后面使用这些节点时,都是用它们的getfather(),可以看做一个编号。
} d[10000000];
int null,cnt,root;
int Trie::num() {if (&d[n]==this) return n;return n=d[n].num();}
int newnode() {++cnt;d[cnt].n=cnt;return cnt;}
void insert(char *s)
{
    int t=root;
    for (;*s!='';++s)
    {
        if (!d[t].c[*s-'0'])
            d[t].c[*s-'0']=newnode();
        t=d[d[t].c[*s-'0']].num();
    }
    d[t].have=1;
}
bool find(char *s)
{
    int t=root;
    for (;*s!='';++s)
    {
        if (!d[t].c[*s-'0'])
            return 0;
        t=d[d[t].c[*s-'0']].num();
    }
    return d[t].have;
}
int open(char *s)//在Trie中开辟出数字串s。
{
    int t=root;
    for (;*s!='';++s)
    {
        if (!d[t].c[*s-'0'])
            d[t].c[*s-'0']=newnode();
        t=d[d[t].c[*s-'0']].num();
    }
    return t;
}
void merge(int t1,int t2)
{
    //这里就不用getfather()了,因为在之前我们已经能够保证它们在并查集中一定是最高的
    if (t1==t2)
        return;
    d[t1].n=t2;//合并
    d[t2].have|=d[t1].have;//试着更新have
    for (int i=0;i<10;++i)
    {
        int son1=d[d[t1].c[i]].num(),son2=d[d[t2].c[i]].num();
        if (son1)
        {
            if (!son2)
                d[t2].c[i]=son1;//如果t1有儿子,t2没儿子,就将转移边连过来。
            else 
            {
                merge(son1,son2);//递归合并
                t2=d[t2].num();//试着更新t2,因为下面的合并有可能影响到t2
            }
        }
    }
}
char str1[8000001],str2[100];
int main()
{
    freopen("quantum.in","r",stdin);
    freopen("quantum.out","w",stdout);
    null=0;
    root=newnode();
    int T;
    scanf("%d",&T);
    while (T--)
    {
        int op;
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%s",str1);
            insert(str1);
        }
        else if (op==2)
        {
            scanf("%s",str1);
            printf("%d
",find(str1));
        }
        else
        {
            scanf("%s %s",str1,str2);
            merge(open(str1),open(str2));
        }
    }
    return 0;
}

总结

这题真的很神奇。
一个好好的Trie,因为各种奇奇怪怪的合并操作将树压成了一个无向图。
注意,上面的t2=d[t2].num();这一句话非常的玄学,不好理解,要特别注意。这就是60分和100分的差距。

原文地址:https://www.cnblogs.com/jz-597/p/11145280.html