并没有怎么做好,时间主要消耗在T1,T2上,从而导致T3并没有什么时间去看。
大概分析了半个多小时分析明白这道题是用LCT动态维护双连通分量。
但是在实现的时候,自己选取的方法(维护两个边表从而来缩点)虽然复杂度是对的,但是常数过大,所以导致顶多卡过去5个点。
void dfs(int rt)
{
if(!rt)return;
dfs(ch[rt][0]);
sta[++top]=rt;
ins[rt]=1;
dfs(ch[rt][1]);
}
void contraction()
{
block++;
while(top)
{
val[n+block]+=val[sta[top]];
for(int i=head[sta[top]],*p=&head[sta[top]];i!=-1;i=edge[i].next,p=&edge[i].next)
{
int to=edge[i].to;
if(ins[to]||del[to]){*p=edge[i].next;continue;}
cut(sta[top],to);
if(!find_root(n+block,to))
{
link(n+block,to);
pa[++patop]=pair<int,int>(n+block,to);
}
*p=edge[i].next;
}
del[sta[top]]=1;
belong[sta[top]]=n+block;
top--;
}
}
void restore()
{
while(patop)
{
edgeadd(pa[patop].first,pa[patop].second);
edgeadd(pa[patop].second,pa[patop].first);
patop--;
}
}
大概就是这样的吧,当时计算的复杂度是基于每条边至多被删一边,被访问两边,并且至多缩n次点。
但是可以发现这个constraction以及restore函数常数巨大,并且多次dfs一棵splay还有可能爆栈。
其实这个边表并不需要维护,直接上一个并查集来维护splay中的缩点情况,每一次缩点的时候是相当于选定一个为根,剩下的所有在该点为根的splay中的点的fa全部指向他,并且贡献全部计算到根上。
这样的好处是常数小,另外不用继续添加新点,直接累加到原来的点上即可。
fa的指向需要注意在access函数中要修改被缩的点的fa,并且在down函数中要判定该点是否为他父亲的儿子。
void down(int rt)
{
if(!rt)return;
if(!is_rt[rt]&&(ch[fa[rt]][0]==rt||ch[fa[rt]][1]==rt))down(fa[rt]);
pushdown(rt);
}
void access(int rt)
{
int y=0;
while(rt)
{
splay(rt);
is_rt[ch[rt][1]]=1,is_rt[y]=0;
ch[rt][1]=y;
pushup(rt);
fa[rt]=s1.find(fa[rt]);
y=rt,rt=fa[rt];
}
}
需要注意的是,如果采用find_root函数,那么效率过低,不妨再写一个并查集来维护树上点的连通情况。
第一发写了个可持久化trie+二分查找,但是发现极限数据跑不出来。
我也不知道是我写渣了还是这个东西的复杂度不对。
为什么我算的明明就是O(len * log(len) * log(n))。
(好像现在知道那个log(len)不靠谱啊!)
结果赶紧再想别的做法。
容易发现,如果我们把这些字符串全部倒过来之后,建立一棵trie树,那么这个时候,按照dfs序排序一下所有的字符串的话,每个字符串成为其他字符串后缀的所有其他字符串就变成了一段连续的区间,而题目询问的就是这段区间中编号第k小的。
所以我们可以按照dfs序排序后,每个位置的权值变为在该位置的字符串的编号。
然后答案就是询问第k小了。
主席树O(nlogn)直接过。
这题没时间细想了,直接敲了个30分暴力上去。
然而这题并没有任何意思,说白了就是一个码农题- -!
我们可以发现,无论怎么操作,给定询问元素的相对大小关系也不变。
所以我们显然可以离线所有询问元素,按照从大到小排序。
接下来的话用线段树维护整个区间即可。
如果是两个log的做法的话,我们可以二分出小于L的点以及大于R的点,将这两段区间直接覆盖成一个值即可。
如果是一个log的做法的话,直接维护一个区间最大值以及区间最小值即可。
判断的时候就是判断这段区间是否全部不会越界,全部不会的话,那么直接区间更新,如果全部会越上界,那么直接覆盖成R,如果全部会越下界,那么直接覆盖成L即可,否则递归修改。
最后询问一下所有的点就行了。
复杂度O(qlogq)