QAQ的LIS树 QAQ的LIS树2 题解报告
这两道题实际上考试的时候是一道题OwO
太可怕了,忙了我三个多小时,写了整整7K
这个题两个询问关联性不强,所以分开来考虑
QAQ的LIS树
考虑如何用dp求解答案
设dp(v)表示v到根的修改后的序列的和,c(v)是v点点权
那么v的答案就是dp(v)减去v到根的点权和
最直观的想法是我们从v点向上暴力跳父亲,跳到第一个不用修改的点u
不难发现因为u没有修改,所以之后的序列和u之后的序列是完全一样的
可以直接转移给v了,那么dp(v)的值就是dp(u)和v->u的序列和了
注意到v->u的所有点我们一定会修改,所以其序列形式一定是c(v),c(v)+1,c(v)+2……
这个数列的和直接等差数列求和即可
准确描述一下u如果要修改应该要改成c(v)+dep(v)-dep(u)
如果u不用修改则满足c(u)>c(v)+dep(v)-dep(u)
移项之后得c(u)+dep(u)>c(v)+dep(v),即找到第一个满足这个条件的点
那么找到第一个不用修改的u的方法就很显然了,我们处理出倍增数组倍增即可
考虑修改的影响,不难发现一个修改影响到的是一棵子树,如果我们暴力重构子树,时间复杂度就爆炸了
我们可以考虑树分块,将树分成若干个块,我们定义这个块中深度最小的点为这个块的根(可以证明这样的点每个块有且仅有一个)
我们对于每个点维护四个信息
第一个信息是这个点到这个块的根的修改后的序列的和
第二个信息是这个点到根修改后根的权值(不难发现这个信息也满足dp性质)
第三个信息是这个点到根的点权和
第四个信息是这个点向上的倍增数组
考虑每次查询块与块之间的拼接
因为我们记录的第二个信息,当跳到上面的块的时候,我们很容易确定当前点拼接到上一个块之后是否需要修改
如果不需要修改,那么直接继续跳到当前块的根即可
否则我们不难得到当前这个点需要修改到的值,同上述的方法倍增找到第一个不需要修改的点并更新答案即可
最后用修改后的序列的和减去点权和就是答案了
考虑修改的影响
每次修改我们只需要暴力重构块内的信息就可以了
对于第三个信息一遍DFS可以搞定,第四个信息重构倍增数组
第一个和第二个信息利用重构后的倍增数组可以重新计算
这样我们就在O(m*sqrt(n)*log(n))的时间内解决了这个问题
但是常数较小,所以跑得还是很快的
QAQ的LIS树2
听何神说这个题目可以用离线+线段树合并搞一搞,但是蒟蒻智商低,并没有听懂
所以自己YY了一个可以在线的O(nlog^2n)的做法
这个询问比上个询问要好想一点
我们考虑一棵子树怎么样才是合法的,当且仅当这棵子树的所有边都是存在的
但是我们如果用0/1表示存在性的话,查询全局的可行解的最大值就变得非常的麻烦
我们不妨将所有不合法的解都变成不可能在查询最大值时查询到的数值,那么我们就可以直接查询最大值了
做法是这样的,我们给每条边都赋一个大于n的权值,每个点一开始都是自己的子树大小
当一条边从合法变成不合法的时候,我们将这条边上面的所有点都减去这个权值
当一条边从不合法变成合法的时候,我们将这条边上面的所有点都加上这个权值
不难发现,一个点的权值是正整数当且仅当子树内所有边均合法
否则因为sz<=n,只要有一条边不合法,其上面所有的点都会减去一个>n的数值变成负数
这用树链剖分+线段树是易于维护的
那么查询就异常的简单了,我们只需要求一下全局的最大值即可(因为不合法的解不可能作为最优解)
对于修改,我们发现每次修改因为修改的是点权,所以影响到的不只是一条边
但是我们会发现我们可以把影响到的边分成两类,一类是当前点到儿子的边,另一类是当前点到父亲的边
不难发现第一类边虽然可能有很多,但是影响到的点都是一样的
我们只需要算出这些影响的综合就可以了,也就是要查询更改前和更改后不合法的边的权值和
不合法的边一定满足c(v)>=c(u),我们对于每个点以c为键值用平衡树维护儿子的信息即可
这样每次在平衡树上直接查就可以了,顺便更改掉其父亲对应的平衡树
之后再判断当前点到父亲的边是否发生了变化并相应的进行修改就可以了
时间复杂度O(nlog^2n)
贴下代码咯,两道题目二合一强行用namespace凑起来
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=100010; const LL oo=1LL<<60; int n,m,f,u,v,blo; int h[maxn],cnt=0; int fa[maxn],top[maxn],son[maxn]; int w[maxn],pos[maxn],fp[maxn],tot=0; int co[maxn],rt[maxn]; bool vis[maxn]; int dep[maxn]; struct edge{ int to,next; }G[maxn]; LL c[maxn],sz[maxn]; LL add[maxn<<2]; LL mx[maxn<<2]; LL xp[maxn]; void Add(int x,int y){++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;} void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } struct Splay_Tree{ int fa[maxn],C[maxn][2]; LL s[maxn],val[maxn],V[maxn]; #define fa(i) fa[i] #define c(x,i) C[x][i] #define s(i) s[i] #define val(i) val[i] void up(int u){ if(!u)return; s(u)=s(c(u,0))+s(c(u,1))+val(u); } void rotate(int p,int x){ int mark= p==c(x,1),y=c(p,mark^1),z=fa(x); if(c(z,0)==x)c(z,0)=p; if(c(z,1)==x)c(z,1)=p; if(y)fa(y)=x; fa(p)=z;c(p,mark^1)=x;fa(x)=p;c(x,mark)=y; up(x); } void Splay(int p,int k,int &rt){ while(fa(p)!=k){ int x=fa(p),y=fa(x); if(y==k)rotate(p,x); else if(p==c(x,0)^x==c(y,0))rotate(p,x),rotate(p,y); else rotate(x,y),rotate(p,x); }up(p);if(!k)rt=p; } void insert(int u,int &rt){ int now=rt,f=now; while(now){ f=now; if(c[u]>V[now])now=c(now,1); else now=c(now,0); } fa(u)=f;V[u]=c[u];val(u)=s(u)=co[u]; if(f)c(f,c[u]>V[f])=u; Splay(u,0,rt); } void del(int u,int &rt){ Splay(u,0,rt); if(!c(u,0)){rt=c(u,1);fa(rt)=0;c(u,1)=0;return;} int now=c(u,0); while(c(now,1))now=c(now,1); Splay(now,u,rt); fa(now)=0;c(u,0)=0;rt=now; fa(c(u,1))=now;c(now,1)=c(u,1);c(u,1)=0; up(now); } int Get_pre(LL v,int &rt){ int now=rt,p=-1; while(now){ if(V[now]<v)p=now,now=c(now,1); else now=c(now,0); }return p; } LL ask(LL v,int &rt){ int pre=Get_pre(v,rt); if(pre==-1)return s(rt); Splay(pre,0,rt); return s(c(rt,1)); } void DFS(int u){ if(!u)return; DFS(c(u,0)); printf("%lld ",V[u]); DFS(c(u,1)); } }T; namespace S{ void DFS(int u,int f){ w[u]=1;sz[u]=co[u]; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; dep[v]=dep[u]+1; DFS(v,u);w[u]+=w[v];sz[u]+=sz[v]; if(w[son[u]]<w[v])son[u]=v; }return; } void Get_pos(int u,int f){ top[u]=f;pos[u]=++tot;fp[tot]=u; if(!son[u])return; Get_pos(son[u],f); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f||v==son[u])continue; Get_pos(v,v); }return; } void Get_Splay(int u,int f){ for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; T.insert(v,rt[u]); Get_Splay(v,u); }return; } void build(int o,int L,int R){ if(L==R){mx[o]=sz[fp[L]];return;} int mid=(L+R)>>1; build(o<<1,L,mid);build(o<<1|1,mid+1,R); mx[o]=max(mx[o<<1],mx[o<<1|1]); } void push_down(int o,int l,int r){ mx[l]+=add[o];add[l]+=add[o]; mx[r]+=add[o];add[r]+=add[o]; add[o]=0; } void UPD(int o,int L,int R,int x,int y,LL v){ if(L>=x&&R<=y){ mx[o]+=v;add[o]+=v; return; } int mid=(L+R)>>1; int l=(o<<1),r=(l|1); if(add[o]!=0)push_down(o,l,r); if(y<=mid)UPD(o<<1,L,mid,x,y,v); else if(x>mid)UPD(o<<1|1,mid+1,R,x,y,v); else UPD(o<<1,L,mid,x,y,v),UPD(o<<1|1,mid+1,R,x,y,v); mx[o]=max(mx[o<<1],mx[o<<1|1]); } void Get_add(int u,LL v){ while(u){ UPD(1,1,n,pos[top[u]],pos[u],v); u=fa[top[u]]; }return; } void Get_modify(int u,LL v){ LL now=T.ask(v,rt[u])-T.ask(c[u],rt[u]); UPD(1,1,n,pos[u],pos[u],now); if(fa[u]){ if(c[u]>=c[fa[u]])now-=co[u]; if(v>=c[fa[u]])now+=co[u]; T.del(u,rt[fa[u]]);c[u]=v; T.insert(u,rt[fa[u]]); Get_add(fa[u],now); }c[u]=v;return; } }; namespace C{ int anc[maxn][10];LL mx[maxn][20]; int rt[maxn],sz[maxn],dep[maxn]; LL go[maxn],co[maxn],s[maxn]; void Get_block(int u,int f){ if(sz[rt[f]]==blo)rt[u]=u,sz[u]=1; else sz[rt[f]]++,rt[u]=rt[f]; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f)continue; dep[v]=dep[u]+1; Get_block(v,u); }return; } void Get_pre(int u,int v){ int now=dep[u]-dep[v]; anc[u][0]=fa[u];mx[u][0]=c[u]+dep[u]; for(int i=1;(1<<(i-1))<=now;++i){ if(anc[u][i-1]!=-1){ int a=anc[u][i-1]; anc[u][i]=anc[a][i-1]; mx[u][i]=max(mx[u][i-1],mx[a][i-1]); } }return; } int Get_mx(int u,int v,int w){ int log,now=dep[u]-dep[v]; for(log=0;(1<<log)<=now;++log);--log; for(int i=log;i>=0;--i){ if(anc[u][i]!=-1&&mx[u][i]<=w)u=anc[u][i]; } if(c[u]+dep[u]<=w)return -1; if(dep[u]<dep[v])return -1; return u; } void Get_ans(int u){ int p=Get_mx(u,rt[u],c[u]+dep[u]); if(p==-1){ go[u]=c[u]*(dep[u]-dep[rt[u]]+1)+xp[dep[u]-dep[rt[u]]]; co[u]=c[u]+dep[u]-dep[rt[u]]; }else{ go[u]=go[p]+c[u]*(dep[u]-dep[p])+xp[dep[u]-dep[p]-1]; co[u]=co[p]; }return; } void DFS_block(int u,int f){ Get_pre(u,rt[u]);Get_ans(u); if(rt[f]==rt[u])s[u]=s[f]+c[u]; else s[u]=c[u]; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(v==f||rt[v]!=rt[u])continue; DFS_block(v,u); }return; } LL Get_cost(int u){ LL ans=go[u]-s[u],la=co[u]; u=fa[rt[u]]; while(u){ if(la<c[u])ans=ans+go[u],la=co[u]; else{ int p=Get_mx(u,rt[u],la+1+dep[u]); if(p==-1){ ans=ans+(la+1)*(dep[u]-dep[rt[u]]+1)+xp[dep[u]-dep[rt[u]]]; la=la+1+dep[u]-dep[rt[u]]; }else{ ans=ans+go[p]+(la+1)*(dep[u]-dep[p])+xp[dep[u]-dep[p]-1]; la=co[p]; } }ans-=s[u];u=fa[rt[u]]; }return ans; } }; int main(){ freopen("increasing.in","r",stdin); freopen("increasing.out","w",stdout); int __size__=128<<20; char *__p__=(char*)malloc(__size__)+__size__; __asm__("movl %0, %%esp\n"::"r"(__p__)); read(n);blo=150; memset(C::anc,-1,sizeof(C::anc)); xp[0]=0; for(int i=1;i<=n;++i)xp[i]=xp[i-1]+i; for(int i=2;i<=n;++i){ read(f);f++; Add(f,i);fa[i]=f; } for(int i=1;i<=n;++i)co[i]=rand()%n+n,co[i]=-co[i]; S::DFS(1,-1);S::Get_pos(1,1); for(int i=1;i<=n;++i)sz[i]+=w[i],sz[i]-=co[i]; S::build(1,1,n);S::Get_Splay(1,-1); C::sz[0]=blo;C::Get_block(1,0); for(int i=1;i<=n;++i)if(C::rt[i]==i)C::DFS_block(i,fa[i]); read(m); while(m--){ char ch=getchar(); while(ch<'!')ch=getchar(); if(ch=='M'){ read(u);read(v);u++; S::Get_modify(u,c[u]+v); C::DFS_block(u,fa[u]); }else if(ch=='S')printf("%lld\n",mx[1]); else{ read(u);u++; printf("%lld\n",C::Get_cost(u)); } }return 0; }
UPD:被梁大爷虐傻了,感觉自己智商被掏空
第一题做法简直是在扯淡OwO有更优秀的做法
一直以为信息是不可合并的,才采用树分块的鬼畜做法
但是信息是可合并的,直接树链剖分可以了