这是一道很巧妙的题目。

今早,我调了好久,终于将它切掉了……


题目

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

题目大意是这样的:

维护一个数字串集合,每一次有三个操作:

1. 插入一个数字串。

2. 询问一个数字串是否在集合中。

3. 纠缠两个数字串(不一定在这些数字串之内),假设这两个数字串分别为A" role="presentation">AA和B" role="presentation">BB,插入一些数字串,使得对于任意集合中的A+C" role="presentation">A+CA+C,都有B+C" role="presentation">B+CB+C也在集合中;对于任意集合中的B+D" role="presentation">B+DB+D,都有A+D" role="presentation">A+DA+D在集合中(有可能纠缠过后会加入无限的数字串)。

解题思路

我才不会告诉你,我一开始看到这题时,没有理解题目大意。我以为这些加入集合的字符串全部合并在了一起,一想到可能要用某些高端的字符串数据结构,我就感到害怕,于是果断地弃掉了这题。

显然,如果只有前两个操作,那么这就是一道裸裸的Trie。

为什么?不知道为什么的同志们,请再次仔细地理解一下题目大意。如果还不知道为什么,就查查Trie到底是个什么东西。说真的,我觉得Trie是一种无师自通的玩意儿。只要你知道Trie是个什么东西,那么前两个操作对于你来说一定不难。

现在的问题是如何搞第三个操作。

既然前两个操作都和Trie建立了联系,那么不妨想想,如何用Trie来解决。

我们在看看所谓“纠缠”的条件。什么是“纠缠”?把Trie上表示A" role="presentation">AA和B" role="presentation">BB点找出来,设为a" role="presentation">aa和b" role="presentation">bb。

那么我们是不是可以理解成,a" role="presentation">aa和b" role="presentation">bb为根的子树,长得一模一样?

可能有些不好想象,感受一下~~~

算了,放张图。



这样可以理解了吗?

就是让以它们为根的子树长得一模一样。

所以有个思路就是,先把a" role="presentation">aa和b" role="presentation">bb找出来,暴力地同化它们的子树。

诶~等等,似乎会超时。

读者:你在逗我?

暴力同化它们的子树,这是不现实的。不仅暴力同化花很多时间空间,而且在以后,当有节点插入时,还要在搞,比蜗牛还慢……

与其这样,不如合并a" role="presentation">aa和b" role="presentation">bb。这样子,下面的就变得一模一样。当后面有插入时,那也可以直接修改,反正两个已经合为一体了。

这个方法非常神奇。显然,它也是正确的。

可能你们会有一堆问题吧,比如,如何合并?



先将a" role="presentation">aa和b" role="presentation">bb找出来,然后合并。再递归它们的儿子,分别合并。(用并查集)

这样的时间复杂度是不会炸的。建Trie所需要的节点是有限的。在没合并之前,它们就是互相独立的块。每次的合并,将会减少一个块。下次再访问它们时,它们已经是一体的了。

无限的情况怎么办?

其实无限的情况是没必要特判的。相信聪明的读者已经发现了,实际上,出现无限的情况,也就是在合并的时候一个节点和它的祖先合并。那么这些Trie上的转移边,实际上就变成了一个有向图。查询的时候,就像以前那样查询就行了。

一棵好好的Trie树,经过各种合并后,形成了一个有向图……是不是很神奇呢?

算法流程

前面的两个操作就不说了,直接将第三个操作的核心——合并。

假设现在要合并以a" role="presentation">aa和b" role="presentation">bb为根的子树。

首先,将a" role="presentation">aa和b" role="presentation">bb各自跳到getfather" role="presentation">getfathergetfather。

如果a=b" role="presentation">a=ba=b则退出。

将a" role="presentation">aa所在的集合,并入b" role="presentation">bb所在的集合中。用a" role="presentation">aa尝试更新b" role="presentation">bb上的have" role="presentation">havehave。(表示这个节点是否有字符串)

然后,枚举它们的儿子。

1. 如果a" role="presentation">aa和b" role="presentation">bb都没有儿子,不理它们。

2. 如果a" role="presentation">aa有儿子,而b" role="presentation">bb没有儿子,那么,将b" role="presentation">bb的这个转移边连过去。

3. 如果a" role="presentation">aa没有儿子,b" role="presentation">bb有儿子,不理它们(因为是用a" role="presentation">aa合并到b" role="presentation">bb中)。

4. 如果两个都有儿子,那么递归合并它们的儿子,合并完后,b" role="presentation">bb跳到它的getfather" role="presentation">getfathergetfather(这一条很玄学,某大佬的解释是:有可能合并完它们的儿子之后,会对已经和b" role="presentation">bb同一个并查集中的节点有影响,因为在各种合并之后,这不再是一棵树,而是有向图)。

代码

千言万语不如一标程。

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!='\0';++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!='\0';++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!='\0';++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\n",find(str1));
}
else
{
scanf("%s %s",str1,str2);
merge(open(str1),open(str2));
}
}
return 0;
}

总结

这题真的很神奇。

一个好好的Trie,因为各种奇奇怪怪的合并操作将树压成了一个无向图。

注意,上面的t2=d[t2].num();这一句话非常的玄学,不好理解,要特别注意。这就是60分和100分的差距。

JZOJ5822 【NOIP提高A组模拟2018.8.16】 量子纠缠的更多相关文章

  1. 5820. 【NOIP提高A组模拟2018.8.16】 非法输入(模拟,字符串)

    5820. [NOIP提高A组模拟2018.8.16] 非法输入 (File IO): input:aplusb.in output:aplusb.out Time Limits: 1000 ms   ...

  2. JZOJ 5818. 【NOIP提高A组模拟2018.8.15】 做运动

    5818. [NOIP提高A组模拟2018.8.15] 做运动 (File IO): input:running.in output:running.out Time Limits: 2000 ms  ...

  3. JZOJ 5812. 【NOIP提高A组模拟2018.8.14】 区间

    5812. [NOIP提高A组模拟2018.8.14] 区间 (File IO): input:range.in output:range.out Time Limits: 1000 ms  Memo ...

  4. [JZOJ5817] 【NOIP提高A组模拟2018.8.15】 抄代码

    Description J 君是机房的红太阳,每次模拟她总是 AK 虐场.然而在 NOIP2117 中,居然出现了另一位 AK 的选手 C 君! 这引起了组委会的怀疑,组委会认为 C 君有抄袭 J 君 ...

  5. [JZOJ5818] 【NOIP提高A组模拟2018.8.15】 做运动

    Description 一天,Y 君在测量体重的时候惊讶的发现,由于常年坐在电脑前认真学习,她的体重有了突 飞猛进的增长. 幸好 Y 君现在退役了,她有大量的时间来做运动,她决定每天从教学楼跑到食堂来 ...

  6. 【NOIP提高A组模拟2018.8.14】 区间

    区间加:差分数组修改 O(n)扫描,负数位置单调不减 #include<iostream> #include<cstring> #include<cstdio> # ...

  7. [jzoj 5782]【NOIP提高A组模拟2018.8.8】 城市猎人 (并查集按秩合并+复杂度分析)

    传送门 Description 有n个城市,标号为1到n,修建道路花费m天,第i天时,若gcd(a,b)=m-i+1,则标号为a的城市和标号为b的城市会建好一条直接相连的道路,有多次询问,每次询问某两 ...

  8. [jzoj 5781]【NOIP提高A组模拟2018.8.8】秘密通道 (最短路)

    传送门 Description 有一副nm的地图,有nm块地,每块是下列四种中的一种: 墙:用#表示,墙有4个面,分别是前面,后面,左面,右面. 起点:用C表示,为主角的起点,是一片空地. 终点:用F ...

  9. [jzoj 5778]【NOIP提高A组模拟2018.8.8】没有硝烟的战争 (博弈论+dp)

    传送门 Description 被污染的灰灰草原上有羊和狼.有N只动物围成一圈,每只动物是羊或狼. 该游戏从其中的一只动物开始,报出[1,K]区间的整数,若上一只动物报出的数是x,下一只动物可以报[x ...

随机推荐

  1. Java 基础 - final vs static

    总结 共同点,都可以修饰类,方法,属性.而不同点: static 含义:表示静态或全局,被修饰的属性和方法属于类,可以用类名.静态属性 / 方法名 访问 static 方法:只能被static方法覆盖 ...

  2. day11 grep正则匹配

    ps aus | trep nginx # 查看所有正在运行的nginx任务 别名路径: alias test_cmd='ls -l' PATH路径: 临时修改: PATH=$PATH:/usr/lo ...

  3. python相关软件安装流程图解——虚拟机安装——CentOS-7-x86_64-DVD-1810——CentOS-01下载——CentOS-02安装——CentOS-03配置操作

    http://www.xitongzhijia.net/soft/24315.html http://www.downxia.com/downinfo/4574.html     .

  4. JSOI 2008 魔兽地图

    题目描述 DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA (Defense of the Ancients) ...

  5. css3 鼠标悬浮动画效果

    CSS3案例 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <ti ...

  6. Windows平台编译libevent

    使用VisualStudio来编译,我的电脑上安装的是VS2013.1.在开始菜单项里面(或者在VS安装路径中)打开Developer Command Prompt for VS2013.exe2.在 ...

  7. (转) Mac下面的SecureCRT(附破解方案) 更新到最新的7.3.7

    Mac下面的SecureCRT(附破解方案) 更新到最新的7.3.7 转自 http://blog.csdn.net/skykingf/article/details/17450561 http:// ...

  8. 09_springmvc图片上传

    一.上传图片 1.需求 在修改商品页面,添加上传商品图片的功能 2.springmvc中对多部件类型解析 在页面form中提交enctype="multipart/form-data&quo ...

  9. python爬取(自动化)豆瓣电影影评,并存储。

    from selenium import webdriverfrom selenium.webdriver import ActionChainsimport time driver = webdri ...

  10. SQL中的左连接与右连接,内连接有什么不同

    SQL中的左连接与右连接,内连接有什么不同 我们来举个例子.天庭上面有一个管理系统:管理系统有个主表:主表记录着各个神仙的基本信息(我们把它当成表A).还有个表记录着他们这个神仙的详细信息(我们把它当 ...