题意:
给出一个nnn个结点的树,初始权值为1,处理qqq个操作,操作只会是以下四种之一
(1)+ x y k+\ x\ y\ k+ x y k 在xxx到yyy的路径上的每个点的权值+k+k+k
(2)∗ x y k*\ x\ y\ k∗ x y k 在xxx到yyy的路径上的每个点的权值∗k*k∗k
(3)− x1 y1 x2 y2-\ x_{1}\ y_{1}\ x_{2}\ y_{2}− x1 y1 x2 y2,删去边(x1,y1)(x_{1},y_{1})(x1,y1),加入边(x2,y2)(x_{2},y_{2})(x2,y2)
(4)/ x y/\ x\ y/ x y,查询xxx到yyy路径上的点的权值和,模51061
方法:
删/连边显然是lct,但权值修改暴力修改超时了一发,需要用懒标记,线段树怎么用在lct就怎么用,sumsumsum成员维护当前splaysplaysplay的总和,所以和线段树一样,需要维护元素个数,线段树可以r−l+1r-l+1r−l+1得到,这里需要多加一个sizesizesize来维护,懒标记直接split(x,y)split(x,y)split(x,y)后放在yyy上即可
懒标记优先级高的先处理,注意pushdownpushdownpushdown之后清空懒标记
对于询问,我们不需要再次下放懒标记了,因为splitsplitsplit的时候先取出了首尾为xxx和yyy的splaysplaysplay,然后再一次splay(y)splay(y)splay(y),splaysplaysplay前会下放懒标记,所以splitsplitsplit完直接查询tree[y].sumtree[y].sumtree[y].sum即可。
另外,对于题目保证合法的link/cutlink/cutlink/cut操作,我们不要再在判断时调用findrootfindrootfindroot了,在这里常数过大会超时。
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int mod=51061;
struct LCT
{
struct node
{
ll v,sum,multitag,addtag;
int tag,son[2],fa,size;
};
stack<int>s;
vector<node>tree;
LCT():tree(100005,{0,0,1,0,0,{0,0},0}){}
bool isroot(int x)
{
return tree[tree[x].fa].son[0]!=x&&tree[tree[x].fa].son[1]!=x;
}
int getson(int x,int y)
{
return x==tree[y].son[1];
}
void push_up(int x)
{
tree[x].size=1;
tree[x].sum=tree[x].v;
for(int i=0;i<=1;i++)
{
tree[x].sum=(tree[x].sum+tree[tree[x].son[i]].sum)%mod;
tree[x].size+=tree[tree[x].son[i]].size;
}
}
void rotate(int x)
{
int y=tree[x].fa,z=tree[y].fa;
int k1=getson(x,y),k2=getson(y,z);
tree[x].fa=z;
if(!isroot(y)) tree[z].son[k2]=x;
tree[y].son[k1]=tree[x].son[!k1];
if(tree[x].son[!k1]) tree[tree[x].son[!k1]].fa=y;
tree[x].son[!k1]=y;
tree[y].fa=x;
push_up(y); push_up(x);
}
void reverse(int x)
{
swap(tree[x].son[0],tree[x].son[1]);
tree[x].tag^=1;
}
void get_tag(int x,int addtag,int multitag)
{
tree[x].sum=(tree[x].sum*multitag+addtag*tree[x].size)%mod;
tree[x].v=(tree[x].v*multitag+addtag)%mod;
tree[x].multitag=(tree[x].multitag*multitag)%mod;
tree[x].addtag=(tree[x].addtag*multitag+addtag)%mod;
}
void push_down(int x)
{
if(tree[x].tag)
{
for(int i=0;i<=1;i++)
if(tree[x].son[i]) reverse(tree[x].son[i]);
tree[x].tag=0;
}
for(int i=0;i<=1;i++)
if(tree[x].son[i]) get_tag(tree[x].son[i],tree[x].addtag,tree[x].multitag);
tree[x].addtag=0;
tree[x].multitag=1;
push_up(x);
}
void splay(int x)
{
int now=x;
s.push(now);
while(!isroot(now))
{
s.push(tree[now].fa);
now=tree[now].fa;
}
while(!s.empty())
{
push_down(s.top());
s.pop();
}
while(!isroot(x))
{
int y=tree[x].fa,z=tree[y].fa;
int k1=getson(x,y),k2=getson(y,z);
if(!isroot(y))
{
if(k1==k2) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x)//打通root->x的splay
{
int last=0;
while(x)
{
splay(x);
tree[x].son[1]=last;
push_up(last=x);
x=tree[x].fa;
}
}
void makeroot(int x)
{
access(x);
splay(x);
reverse(x);
}
int findroot(int x)
{
access(x);
splay(x);
while(tree[x].son[0])
{
push_down(x);
x=tree[x].son[0];
}
splay(x);
return x;
}
void split(int x,int y)
{
makeroot(x);
access(y);
splay(y);
}
void link(int x,int y)
{
makeroot(x);
// if(x==findroot(y)) return;
tree[x].fa=y;
}
void cut(int x,int y)
{
makeroot(x);
if(findroot(y)!=x||tree[y].fa!=x||tree[y].son[0]) return;
tree[y].fa=tree[x].son[0]=0;
}
}lct;
signed main()
{
// freopen("in.txt","r",stdin);
int n,q; cin>>n>>q;
for(int i=1;i<=n;i++) lct.tree[i].v=1;
for(int i=1;i<n;i++)
{
int x,y; scanf("%lld%lld",&x,&y);
lct.link(x,y);
}
while(q--)
{
char s[20]; scanf("%s",s+1);
if(s[1]=='+')
{
int x,y,k; scanf("%lld%lld%lld",&x,&y,&k);
lct.split(x,y);
lct.get_tag(y,k,1);
}
else if(s[1]=='*')
{
int x,y,k; scanf("%lld%lld%lld",&x,&y,&k);
lct.split(x,y);
lct.get_tag(y,0,k);
}
else if(s[1]=='-')
{
int x,y; scanf("%lld%lld",&x,&y);
lct.cut(x,y);
scanf("%lld%lld",&x,&y);
lct.link(x,y);
}
else
{
int x,y; scanf("%lld%lld",&x,&y);
lct.split(x,y);
printf("%lld\n",lct.tree[y].sum);
}
}
return 0;
}
这篇博客介绍了如何使用LCT(Link-Cut Tree)数据结构结合懒惰标记处理权值修改问题。在处理树的边的增删和权值修改操作时,利用线段树的懒惰标记策略来优化效率。博客详细阐述了LCT的基本操作,如旋转、反转、下推、获取路径和、建立根节点等,并展示了如何在LCT中实现懒惰标记。最后,给出了处理权值修改和查询的具体实现代码,强调了合法操作的判断和常数优化的重要性。
958

被折叠的 条评论
为什么被折叠?



