原理
懒得讲了
应用(你会发现这就是(fAKe_Hu)的题单)
维护链信息
维护链懒标记的裸题
每个点向右边弹到的点连边,超过(n)的所有点连向(n+1),最后形成一棵树,一个点(splay)到根后左儿子的数量(深度)就是最后答案
记录一个节点的权值为(0-3)表示接受的(1)状态的数量
考虑每次更改一次节点状态,会自底部向上修改连续一段节点的状态
我们可以平衡树上二分找第一个权值为(1/2)的节点,也可以直接数组记录,然后把它换到根
注意修改完之后记录(1/2)的数组要交换,如果这个节点不存在就把整条链都修改
动态维护连通性
裸题,(link)和(cut)操作
常见套路是删边变倒序加边
先将没有被删去的边连起来,我们把每条边中间加一个点,然后把新加的点且在原图中是割点的的点权都变成(1)
倒序操作序列,加边的时候如果两个点不连通就直接连边,让新加的点边权为(1)
否则把那个点到根的权值全部变为(0)
查询操作就是两点间的权值
(1)操作:连接两个跑道,如果已经联通,那么就把路径上所有点用并查集合并,把所有点的权值给根节点
(2)操作:修改该节点根节点权值
(3)操作:求链和
没写,口胡的(
动态维护生成树
删边变逆序加边
(link)一条边时,如果两点已经联通,那么二分找到边权最大的边(边化点),然后替换掉
将边权从小到大排序插入(LCT),形成生成树之后每次把边权最小的边换掉,计算答案
将边按一个关键字排序,维护另一个关键字的最小生成树
维护子树
求一条边两个端点虚子树点数之和
其实就两步,(link)的时候一个节点加上另一个节点总儿子数,(access)的时候加上原右子树减去新右子树
这是个神仙状态
(dp1[x],dp2[x])分别表示在(splay)中(x)子树里面深度最浅的点最近的白点的距离 和 深度最深的点最近的白点的距离
每个节点开个(multiset)维护下虚子树内白色点的最小距离
inline int fir(int x)
{
return (!q[x].empty())?(*q[x].begin()):inf;
}
inline void pushup(int x)
{
str[x]=str[son[x][0]]+str[son[x][1]]+1;
dp1[x]=min(dp1[son[x][0]],str[son[x][0]]+min(val[x]?0:inf,min(fir(x),dp1[son[x][1]]+1)));
dp2[x]=min(dp2[son[x][1]],str[son[x][1]]+min(val[x]?0:inf,min(fir(x),dp2[son[x][0]]+1)));
}
注意(access)的时候更新(multiset)
inline void access(int x)
{
for(int y=0;x;x=f[y=x])
{
splay(x);
q[x].insert(dp1[son[x][1]]+1);
son[x][1]=y;
it=q[x].lower_bound(dp1[son[x][1]]+1);
if(it!=q[x].end()&&(*it)==dp1[son[x][1]]+1) q[x].erase(it);
pushup(x);
}
}
最后答案是将(x)旋转到根之后的(dp2[x])
我们在这里选择不(makeroot),因为需要翻转子树很麻烦,修改时直接修改(val[x])的状态然后(pushup(x))
包括(link)和(cut)操作的维护子树
注意点就是(reverse)的时候要把(dp1)和(dp2)一起交换,同时(cut)的时候必须把连个点都喝边代表的虚点断开,否则边权会随机加到一棵(splay)上
维护染色联通块
每种颜色开一个(LCT),大力维护就行了
我们仍然考虑每个(splay)维护同一种颜色
(1)操作:其实就是一个(access)操作
(2)操作:考虑一个节点到根节点的权值,就是这个节点到根节点的虚边数量,但是每次(access)更新时会更新一整个子树,所以我们考虑(dfs)序上建线段树维护,询问时考虑树
上差分,由于每个颜色都是到根节点的一条链,所以可以直接差分
(3)操作:(dfs)序上区间最值问题
点化边模型,显然暴力操作会被菊花图卡到自闭,我们考虑把每个点的颜色给这个点在树上的父亲边
(1)号节点没有父边,需要加一个虚拟节点
变化颜色时将该店与父亲节点的边更换(LCT),这样发现每个连通块中除了根节点都是同一颜色
查询时输出根节点的右儿子的答案
奇怪操作
四个状态,(0)从根走到当前,(1)从根走到当前,(0)从当前到根,(1)从当前到根
合并的式子是
t0=(~x.t0&y.t0)|(x.t0&y.t1);
t1=(~x.t1&y.t0)|(x.t1&y.t1);
对于(0)起始,就是前一段是(0)的位走后一段(0)起始的答案或上前一段是(1)的位走后一段(1)起始的答案,(1)起始同理
没了
考虑到对于一段路径((x,y)),如果所有路径都经过((x,y)),那么以(x)为根,必然都有且只有一个端点在(y)子树内
每条路径(rand)一个权值出来,将路径的两个端点异或上这个权值,(split(x,y))之后判断(x)子树异或和是否等于所有边权异或和
逐个操作分析。。
(1)操作,显然对于所有树都长一个节点是无所谓的
(2)操作就讲究了,会发现(l)和(l-1)颗树的区别只是一大堆节点长哪上面了,转移一大堆节点我们可以套路的弄个虚点出来,把虚点连生长节点下面,生长的时候长虚点下面,更
改生长节点的时候把虚点换个位置就行了
查询:规定了根节点的情况下并不能换根,考虑树上差分,对于虚点点权应该为(0),这里有个小技巧:(access)求(lca)
先(access(x)),再(access(y)),最后一次操作的(y)节点就是(lca)
考虑每个点的贡献,对于一个点来说,最优的操作方式肯定是一个每个子树内的点交替(access),因为连续两个同一子树内的点(access)不对答案作出贡献
然而有可能某个子树点数过大导致无法实现。
具体来说,一个点对答案的贡献是(min{S_x-1,2*(S_x-max{S_{son_x}})})
(max{S_{son_x}}>frac{S_x+1}{2})时(min)取后面
但是我们还有修改操作
对于一个点(a_i+w),能影响的只有这个点到根的路径,如果路径中本身就有些点子树过大,那么是不用修改的
哪些点用修改呢?就是将整棵树重链剖分后的轻边的父节点,这些点不超过(logn)个
证明完复杂度我们可以用(lct)的(access)来完成对路径上虚边的判断
一些细节可以参照(fAKe_Hu)的(code)。。。
乱七八糟的东西不好维护,考虑每个点维护一个多项式
所以我们把(e^{ax+b})和(sin(ax+b))泰勒展开一下子变成多项式维护起来
然后就基本裸(lct)了