有一颗n个节点的树,每个节点有编号与权值。有m次操作,每种操作都有独特的编号。
编号为1的操作,会切断当前树上存在的一条边,并新加一条边,保证操作完成后仍然是树。
编号为2的操作,会改变这颗树的根节点(初始根节点为1)。
编号为3的操作,会给树上一条路径上所有点的权值都增加x。
编号为4的操作,会对树上一条路径上点的权值信息进行轮换,如果是对j到k这条路径操作,从j走到k的遍历序列是a1~p。则a1的权值改为a2的权值,a2的权值改为a3的权值……ap-1的权值改为ap的权值,ap的权重改为a1的权值。
编号为5的操作,会对树上一条路径上所有点的权值都开方(下取整)
编号为6的操作,会询问树上一条路径上所有点的权值和。
编号为7的操作,会给定两个常数p和q。你需要求出两个正整数u和v,对于给定路径上任意两点权值x和y,需要满足x/p
毒瘤题···不仅操作分析要很久,打起来打错了要到处找。作为一个才打过两道LCT的人···一上来就搞这样的东西,有点伤。进入正题:
从简单的开始讲吧。
“切断当前树上存在的一条边,并新加一条边,保证操作完成后仍然是树。”
LCT直接link和cut。
“改变这颗树的根节点(初始根节点为1)。”
直接记录,因为其他操作也要makeroot呢,只需要在询问子树大小的时候重新makeroot这个点就好了。
区间加,LCT直接弄出路径之后打加法标记。
区间查找,也是弄出路径直接搞就好了。
“给定两个常数p和q···”
可以看出它卡出来的区间跟最大最小值有关,那么先求出最大最小值,接着用类欧求。可以发现让v或者u最小是等价的,分数嘛,那么我们考虑这样的问题 求最小x,y,满足ab<xy<cd 设这个局面为f(a,b,c,d),那么考虑几个局面:
1,a=0,这样的话x肯定为1,那么y我们可以通过限制算出,由 x=1y<cd 得 y=floor(d/c)+1 。
2,a>b,我们可以先减掉一个整数,那么转移到f(a%b,b,c-(a/b),d),子问题的解中的x+=y*floor(a/b)即为当前局面解。
3,a
“询问一个子树大小”
这是一个经典的LCT信息维护。具体的,我们对树上每个点维护轻边连出去的子树的点数和,记为ltsiz。为了使其可以被维护,还需要一个ltsum,为当前点的splay子树的ltsiz的和。
注意先makeroot(当前树真正的根)
现在考虑几个需要更新这两个信息的地方:
假设我们做到当前重链的顶端节点为x,那么他要和他在树中的父亲tup[x]相连,tup[x]则要断开原本splay中的右子树。对于前者,轻链变成了重链,那么tup[x]的ltsiz就要减去x在树中的整个子树的大小,大小应该是x.ltsum+splay_size[x];后者也类似,只是变成加上。这里要注意的是要把重链本身的点也加上。
接一条轻链,类似于前面
cut操作里面断开的是重链,只需要把ltsum更新一下就好了。
这样8操作就严谨完成了。
“区间开方”
假设现在线段树上做吧。
先不考虑跟其他操作混在一起。假如只有操作5和6,很显然一个数开方几次之后就变成了1,可以看做常数。那么我们可以暴力开方,如果区间全是一就不递归下去。复杂度是nlogn的。
假如操作5和3混在一起,情况就稍微有些复杂了。仍然考虑一个区间怎么处理。哎假如一个区间开方之后一样,即最大值和最小值开方后一样,那么我们可以直接打赋值标记嘛。如果我们把这样的一段区间成为颜色段,那么一个区间可能有多个颜色段,复杂度应该和颜色段数有关。那么考虑总复杂度的话,就要先考虑颜色端开方几次会合并。
可以发现对于两个颜色段 a 和 b,有(√a-√b)(√a+√b)=a-b,而√a-√b<√a+√b。那么它们的差起码也开了个方,也就是说没几次他们就合并了。然后再考虑:
加法标记,它相当于在加的区间的开头和结尾断开颜色段,也就是说最多断开两个。
噢耶,又可以暴力做了!
根据出题人说这里有个trick,就是开方下取整,可能导致原本差是1开方完之后还是1,然后再套上加法标记,颜色端永远不能合并,可能会TLE,就比如序列 3 4 3 4 3 4,开放之后又加上2,就没办法了。这种情况也很简单,因为两次差一样,直接打区间加法标记。就没事了。
为什么其他的情况就不用考虑T不T呢,因为其他情况下差开方不可能等于本身。
势能分析一下吧:设势能函数φ表示每个相邻颜色段合并的所需开方次数的和。那么每次区间加最多加个10,每次开方会减掉区间颜色段数cnt这么多,而一次开方的复杂度又恰好为cnt*logn。也就说总复杂度就是φ增量的和再乘logn,那么φ增量和最大多少?10m+5n。总复杂度 O(nlogn) ,很舒服。
套上LCT怎么做呢?其实一样的,不过因为splay可能由于结构的原因切断了一些颜色块,我们可以一块一块处理。不过因为这是毒瘤题,我不管了直接dfs,反正n这么小。
最后一个很关键了,这是轮换。
序列上的轮换,可以直接用splay来实现,只需要把第一个点移到最后就可以了。
放到树上就比较麻烦了。在序列上的splay里储存的是权值,也就是说那个splay里面是没有编号的,记为权值splay——tr0。实际上,splay上的点的中序遍历的编号,就是序列上的编号。而树上点的编号不是按顺序的,不能直接用中序遍历得出。我们要多建一颗编号splay,记为tr1,让tr0和tr1同步。轮换操作只对tr0进行,tr1不动。
整理一下,我们LCT中,有很多轻边,这些轻边的信息对应的是tr1的东西,我们许多操作都在tr0上,tr1相当于充当其他信息对tr0的中介。
又要在LCT模板上修改了吗····考虑细节。
我们弄个数组ref[2][N],代表tr0的根节点们和tr1的根节点们的一一映射,注意不是相同节点。那每次spaly之后都要更新ref。然后每次对tr1的x节点操作的时候,要在tr0相应的vx节点操作。(LCT时间复杂度可能带多一个log,不过势能分析的话估计均摊仍然不变吧)
哦,轮换操作移动的方法是,把第一个点旋到根断开右子树再让断开那棵子树的最后一个点旋到顶,其右子树再接上原来的第一个点,更新这两个点的信息即可。
现在再来看看操作6和其他操作组合后需要注意的东西:tr1应该记录ltsiz和ltsum,tr0记录点权,和,赋值、加法标记,最值等。
哇,讲完了。感觉这题写了1天啊·····
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=100005;
struct vtree
{
int val,mn,mx;
ll sum;
int eva,add; //tag
}vtr[N];// vtr的点编号是独立的
struct ntree
{
int ltsiz,ltsum;
}ntr[N]; // ntr的点编号就是原编号
int tr[2][N][2],fa[2][N],tag[2][N],tsiz[2][N];
int ref[2][N],tup[2][N],root,st,sta[N];
// ref 为ntr的根对应的vtr的根,rev【vtr点编号】
int n,m,i,v[N],x,y,z,t,kase,ry,l;
int tt,b[N],first[N],next[N];
ll xx,yy,v1,v2,mn,mx;
void cr(int x,int y)
{
tt++;
b[tt]=y;
next[tt]=first[x];
first[x]=tt;
}
void dfs(int x,int y)
{
ref[0][x]=ref[1][x]=x;
tup[1][x]=tup[0][x]=y;
vtr[x].sum=vtr[x].val=vtr[x].mx=vtr[x].mn=v[x];
tsiz[0][x]=tsiz[1][x]=1;
int p;
for(p=first[x];p;p=next[p])
if (b[p]!=y)
{
dfs(b[p],x);
ntr[x].ltsiz+=ntr[b[p]].ltsiz+1;
}
ntr[x].ltsum=ntr[x].ltsiz;
}
void init()
{
scanf("%d %d",&n,&m);
fo(i,1,n-1)
{
scanf("%d %d",&x,&y);
cr(x,y);
cr(y,x);
}
fo(i,1,n) scanf("%d",v+i);
root=1;
dfs(1,0);
}
void down(int x,int z)
{
if (!x) return;
int tmp=tsiz[z][x],l;
int (*y)=tr[z][x];
if (tag[z][x])
{
swap(tr[z][x][0],tr[z][x][1]);
fo(l,0,1) if (y[l])
tag[z][y[l]]^=1;
tag[z][x]=0;
}
if (z==0)
{
if (vtr[x].eva)
{
vtr[x].mn=vtr[x].mx=vtr[x].val=vtr[x].eva;
vtr[x].sum=(ll)tmp*vtr[x].eva;
fo(l,0,1) if (y[l])
vtr[y[l]].add=0,vtr[y[l]].eva=vtr[x].eva;
vtr[x].eva=0;
}
if (vtr[x].add)
{
int dur=vtr[x].add;
vtr[x].mn+=dur;
vtr[x].mx+=dur;
vtr[x].val+=dur;
vtr[x].sum+=(ll)dur*tmp;
fo(l,0,1) if (y[l])
vtr[y[l]].add+=dur;
vtr[x].add=0;
}
}
}
void update(int x,int z)
{
down(x,z);
int (*y)=tr[z][x],l;
fo(l,0,1) if (y[l])
down(y[l],z);
tsiz[z][x]=1+tsiz[z][y[0]]+tsiz[z][y[1]];
if (z==1)
ntr[x].ltsum=ntr[x].ltsiz+ntr[y[0]].ltsum+ntr[y[1]].ltsum;
if (z==0)
{
vtr[x].sum=(ll)vtr[x].val+vtr[y[0]].sum+vtr[y[1]].sum;
vtr[x].mx=vtr[x].mn=vtr[x].val;
fo(l,0,1) if (y[l])
{
vtr[x].mx=max(vtr[x].mx,vtr[y[l]].mx);
vtr[x].mn=min(vtr[x].mn,vtr[y[l]].mn);
}
}
}
int pd(int x,int z)
{
return tr[z][fa[z][x]][1]==x;
}
void rotate(int x,int z)
{
int nz=pd(x,z),y=fa[z][x];
down(x,z);
down(y,z);
fa[z][x]=fa[z][y];
if (fa[z][x]) tr[z][fa[z][y]][pd(y,z)]=x;
tr[z][y][nz]=tr[z][x][1-nz];
if (tr[z][y][nz]) fa[z][tr[z][y][nz]]=y;
tr[z][x][1-nz]=y;
fa[z][y]=x;
update(y,z);
update(x,z);
if (tup[z][y]) tup[z][x]=tup[z][y];
if (ref[z][y]) ref[z][x]=ref[z][y];
tup[z][y]=0;
ref[z][y]=0;
}
void remove(int x,int y,int z)
{
st=1;
sta[1]=x;
while (x!=y)
sta[++st]=(x=fa[z][x]);
while (st)
down(sta[st--],z);
}
void splay(int x,int y,int z)
{
remove(x,y,z);
while (fa[z][x]!=y)
{
if (fa[z][fa[z][x]]!=y)
{
if (pd(x,z)==pd(fa[z][x],z))
rotate(fa[z][x],z);
else rotate(x,z);
}
rotate(x,z);
}
}
int find(int x,int siz,int z)
{
int tmp,k1=x,k2=z;
while (x)
{
down(x,z);
tmp=tsiz[z][tr[z][x][0]];
if (tmp+1==siz) return x;
if (tmp+1x=tr[z][x][1];
siz-=tmp+1;
}else x=tr[z][x][0];
}
printf("萎了 %d %d\n",k1,k2);
}
int reflect(int x)
{
return find(ref[1][x],1+tsiz[1][tr[1][x][0]],0);
}
int access(int x)
{
splay(x,0,1);
int last=0,z=x,vx=reflect(x),vlast=0,vz=vx;
int *y,*vy;
while (x!=0)
{
splay(vx,0,0);
ref[1][x]=vx;//按理来说这两部应该不用
ref[0][vx]=x;
y=tr[1][x];
vy=tr[0][vx];
fa[0][vy[1]]=0;
fa[1][y[1]]=0;
if (y[1])
{
//特别注意, ref ,rev
tup[1][y[1]]=x;
//tup[0][vy[1]]=vx;
ntr[x].ltsiz+=tsiz[1][y[1]]+ntr[y[1]].ltsum;
ref[1][y[1]]=vy[1];
ref[0][vy[1]]=y[1];
}
vy[1]=vlast;
y[1]=last;
ntr[x].ltsiz-=tsiz[1][y[1]]+ntr[y[1]].ltsum;
tup[1][last]=0;
//tup[0][vlast]=0;
if (last)
fa[0][vlast]=vx,
fa[1][last]=x;
update(x,1);
update(vx,0);
last=x;
vlast=vx;
x=tup[1][x];
splay(x,0,1);
if (x) vx=reflect(x);
}
splay(z,0,1);
splay(vz,0,0);
ref[1][z]=vz;//按理来说这两部应该不用
ref[0][vz]=z;
return vz;
}
int makeroot(int x)
{
int vx=access(x);
tag[0][vx]^=1;
tag[1][x]^=1;
return vx;
}
void cut(int x,int y)
{
int vx=makeroot(x);
int vy=access(y);
if (tr[0][vy][0])
{
ref[1][tr[1][y][0]]=tr[0][vy][0];
ref[0][tr[0][vy][0]]=tr[1][y][0];
}
tr[0][vy][0]=0;
tr[1][y][0]=0;
fa[0][vx]=0;
fa[1][x]=0;
update(vy,0);
update(y,1);
splay(vy,0,0);
splay(y,0,1);// ltsiz好像不用处理
ref[1][y]=vy;//按理来说这两部应该不用
ref[0][vy]=y;
}
void link(int x,int y)
{
int vx=makeroot(x);
int vy=access(y);
//tup[0][vx]=vy;
tup[1][x]=y;
ntr[y].ltsiz+=ntr[x].ltsum+tsiz[1][x];
ntr[y].ltsum+=ntr[x].ltsum+tsiz[1][x];
}
void vchange(int x)
{
if (tsiz[0][x]==1) return;
int tmp=find(x,tsiz[0][x],0);
int dur=find(tmp,1,0);
splay(dur,0,0);
fa[0][tr[0][dur][1]]=0;
tr[0][dur][1]=0;
splay(tmp,0,0);
ref[1][y]=tmp;
tr[0][tmp][1]=dur;
fa[0][dur]=tmp;
update(dur,0);
update(tmp,0);
}
void fuck(int x)//会不会漏了一些要down的地方
{
if (!x) return;
down(x,0);
int v1=trunc(sqrt(vtr[x].mn)),v2=trunc(sqrt(vtr[x].mx));
if (v1==v2)
{
vtr[x].add=0;
vtr[x].eva=v1;
down(x,0);
return;
}
if (v1+1==v2&&vtr[x].mn+1==vtr[x].mx)
{
vtr[x].add+=-vtr[x].mx+v2;
down(x,0);
return;
}
fuck(tr[0][x][0]);
fuck(tr[0][x][1]);
vtr[x].val=trunc(sqrt(vtr[x].val));
update(x,0);
}
void likegcd(int a,int b,int c,int d)
{
if (a==0)
{
xx=1;
yy=(d/c)+1;
return;
}
if (a>b)
{
likegcd(a%b,b,c-d*(a/b),d);
xx+=yy*(a/b);
return;
}
if (c>d)
{
xx=yy=1;
return;
}
likegcd(d,c,b,a);
swap(xx,yy);
}
void solve()
{
fo(l,1,m)
{
scanf("%d",&kase);
if (kase==2)
scanf("%d",&root);
else if (kase==8)
{
scanf("%d",&x);
makeroot(root);
access(x);// splayed
printf("%d\n",ntr[x].ltsiz+1);
}
else
{
scanf("%d %d",&x,&y);
if (kase==1)
{
scanf("%d %d",&z,&t);
cut(x,y);
link(z,t);
continue;
}
makeroot(x);
access(y);//splayed
ry=reflect(y);
splay(ry,0,0);//每次splay 完都要更新ref
splay(y,0,1);
ref[1][y]=ry;
ref[0][ry]=y;
down(ry,0);//?? down只用vtr
if (kase==3)
{
scanf("%d",&z);
vtr[ry].add+=z;
}
if (kase==4)
vchange(ry);// 第一个移到最后去。
if (kase==5)
fuck(ry);
if (kase==6)
printf("%lld\n",vtr[ry].sum);
if (kase==7)
{
scanf("%lld %lld",&v1,&v2);
mn=vtr[ry].mn;mx=vtr[ry].mx;
if ((ll)mx*v2>(ll)mn*v1)
{
swap(mx,mn);
swap(v1,v2);
}
if ((ll)mx*v2==(ll)(mn)*v1)
{
printf("-1\n");
continue;
}
xx=0;
yy=0;
likegcd(mx,v1,mn,v2);
printf("%lld %lld\n",xx,yy);
}
}
}
}
int main()
{
freopen("satori.in","r",stdin);
freopen("satori.out","w",stdout);
init();
solve();
}
我再也不写毒瘤题了(划掉