讲道理,基于哈希值的字符串算法真的很不错。因为unsigned long long高达1e19,而算法竞赛中字符串长度不可能大于1e6,毕竟如果再大,那就连经典字符串算法都无法在短时间求得结果了O(nlongn)。所以重复的概率为1e6*1e6/1e19=1e-7,即只有0.00001%的概率会重复。
在本题中HASH值取3是可以的,而2是不可以的。因为2是unsigned long long最大值的因数(unsigned long long自然溢出相当于对2^64取模,或者把HASH取2理解为左移一位的意思),而3不是。
一开始自己维护hash还用什么快速幂,其实一开始先预处理出来就OK了,这就是经验不足了。
自己最开始的想法还更糟糕呢,竟然想到每次问问题时才来打表。因为自己可能觉得树上维护hash值太难了吧。。只能说明自己其实对基于HASH值得LCP算法不理解(一开始觉得概率算法不靠谱所以只是看了一遍,所用总想着用经典的字符串算法,但是事实上基于HASH的LCP太棒了。),所以不知道该如何应用递推式,所以才觉得难,事实上等自己理解了后也就不会觉得那么难了。至于其他的操作就相对简单了,毕竟上一题就是这些啊。
然后一些技巧吧,用中序遍历的话反转就很简单啦,交换左右子树并留下延迟标记,在需要进入下一层时才下方延迟标记并更新子树。同时维护正反两个hash值在反转的时候也只用swap一下而不是重新计算。求子串的hash值也十分巧妙,竟然是先将子串split出来读取hash值后再merge回去。毕竟没有别的更好的办法了,因为伸展树的形态是不确定的,从而导致你想要的子串很可能不是某棵子树,你想要的hash值甚至还没算出来呢,一定要在split的过程中,才能顺带算出来,再说split时间复杂度又不高才O(logn)。是longn而不是nlogn呀,真的很快了。
代码
#include<bits/stdc++.h>
#define maxn 200010
#define MAXN 400010
using namespace std;
typedef unsigned long long ull;
const ull HASH = 2;
ull mp[MAXN];
struct Node
{
Node* ch[2];
int v,s,r;
unsigned long long