题解 P3273 【[SCOI2011]棘手的操作】

feilongz

2017-09-05 19:01:39

Solution

18本 楼上 @远航之曲 的题解非常正确。虽然这道题有更优秀的线段数解法,但是毕竟可以作为可并堆裸题。只是这道题左偏树的复杂度值得商榷。因为左偏树并不能保持平衡,其深度最差可能是O(n),而寻找某个节点的堆顶需要跳到顶,这个操作的效率可能被退化成O(n)。 一些类似的题目往往不需要。 但是根据bzojdiscuss里的说法,貌似数据是特别设计的,左偏树可以跑过,我就不清楚了。 楼上题解左偏树的写法,有一个下传标记操作,在Del操作和Add操作之前都需要下传标记。我这里有一种不同的方法。 对于每个存在的堆,只会维护一个标记,这个标记的数值表示这个堆中的所有元素都加了这个值。 也就是说,堆中某个点的实际值为点值+标记值。 你可以理解为,任何一个节点,如果不是某个堆得根节点,那么它绝对不会有mark(==0)。为了实现这一点,每次Merge的时候,并不是合并某两个具体的点,而只是合并他们的根节点。每次合并,只会在两个根节点上有标记需要考虑(其他点都没有mark啊。) 合并的时候,我们只能保留一个标记,另一个标记就必须暴力下传到每一个点。这里如果随便下传一个堆,保留另一个堆的mark,复杂度就不能保证。可以用一种类似启发式合并的方法,对堆中每个节点维护一个size,合并标记的时候选择小的堆下传,大的堆标记保留。这样总复杂度是可以证明的。 另外,要明白合并之后,两个堆公用一个标记(现在是一个堆了),我们还需要在下传小堆标记的时候减去大堆标记的影响。 例如,小堆的某个点是2,mark为1,那么这个点的实际值为3.但是如果大堆有一个MARK=3,合并之后共用标记,这个点的值就会变成6,显然是不可以的(因为合并前大堆的标记只对大堆有效)。因此下传小堆标记的时候要下传mark-MARK,这样下传之后该点的值为0,统计的时候加上MARK=3,正好等于3. 具体还有细节需要注意。上代码。(理论复杂度并不慢,只是我自带大常数。) ```cpp #include<cstdio> #include<iostream> #include<queue> using namespace std; struct heap { struct node { int val,dist,size; int l,r,fa; }e[301000]; int ROOT; int mark[301000]; int getroot(int x) { while(e[x].fa) x=e[x].fa; return x; } void pushup(int x) { if(!x) return; e[x].size=1; if(e[x].l) e[x].size+=e[e[x].l].size; if(e[x].r) e[x].size+=e[e[x].r].size; } void pushdown(int root,int val) { e[root].val+=val; if(e[root].l) pushdown(e[root].l,val); if(e[root].r) pushdown(e[root].r,val); } void Mergemark(int x,int y) { int fx=getroot(x); int fy=getroot(y); if(fx==fy) return; int Y=fy,X=fx; if(e[fx].size<e[fy].size) swap(X,Y); pushdown(Y,mark[Y]-mark[X]); if(e[fx].val<e[fy].val) swap(fx,fy); mark[fx]=mark[X]; mark[fy]=0; } int Merge2(int x,int y) { if(!x) return y; if(!y) return x; int fx=getroot(x); if(fx==getroot(y)) return fx; if(e[x].val<e[y].val) swap(x,y); // down(x); e[x].r=Merge2(e[x].r,y); e[e[x].r].fa=x; if(e[e[x].r].dist>e[e[x].l].dist) swap(e[x].l,e[x].r); if(!e[x].r) e[x].dist=0; else e[x].dist=e[e[x].r].dist+1; pushup(x); return x; } int Merge(int x,int y) { Mergemark(x,y); return Merge2(x,y); } int Del(int x) { if(!x) return 0; e[e[x].l].fa=0; e[e[x].r].fa=0; int q=e[x].fa,p=Merge(e[x].l,e[x].r); pushup(p); e[p].fa=q; if(q&&e[q].l==x) e[q].l=p; if(q&&e[q].r==x) e[q].r=p; if(!q) mark[p]=mark[x]; while(q) { if (e[e[q].l].dist<e[e[q].r].dist) swap(e[q].l,e[q].r); if (e[e[q].r].dist+1==e[q].dist) return ROOT; e[q].dist=e[e[q].r].dist+1; p=q; q=e[q].fa; pushup(q); } return p; } void re(int x,int val) { mark[x]=0; e[x].fa=0; e[x].size=1; e[x].val=val; e[x].dist=0; e[x].l=e[x].r=0; } void Add(int x,int val) { re(x,val); if(!ROOT) ROOT=x; else ROOT=Merge(ROOT,x); } }hp1,hp2; int n; void Merge(int x,int y) { int fx=hp1.getroot(x); int fy=hp1.getroot(y); if(fx==fy) return; hp2.ROOT=hp2.Del(fx); hp2.ROOT=hp2.Del(fy); int z=hp1.Merge(fx,fy); hp2.Add(z,hp1.e[z].val+hp1.mark[z]); } void Addpoint(int x,int v) { int fx=hp1.getroot(x); hp2.ROOT=hp2.Del(fx); int val=hp1.e[x].val; if(fx==x) if(!hp1.e[x].l&&!hp1.e[x].r) { hp1.e[x].val+=v; hp2.Add(x,hp1.e[x].val+hp1.mark[x]); return; } else { if(hp1.e[x].l) fx=hp1.e[x].l; if(hp1.e[x].r) fx=hp1.e[x].r; } hp1.Del(x); int z=hp1.getroot(fx); hp1.re(x,hp1.e[x].val+v+hp1.mark[z]),z=hp1.Merge(x,z); hp2.Add(z,hp1.e[z].val+hp1.mark[z]); } void Addlink(int x,int v) { int fx=hp1.getroot(x); hp1.mark[fx]+=v; hp2.ROOT=hp2.Del(fx); hp2.Add(fx,hp1.e[fx].val+hp1.mark[fx]); } int buff=0; void Addheap(int v) { buff+=v; } int Getpoint(int x) { int addition=hp1.mark[hp1.getroot(x)]; return hp1.e[x].val+addition+buff; } int Getlink(int x) { int fx=hp1.getroot(x); return hp1.mark[fx]+hp1.e[fx].val+buff; } int Getheap() { return hp2.e[hp2.ROOT].val+buff; } int a[301000]; queue<int> q; int main() { scanf("%d",&n); hp2.e[0].dist=-1; hp1.e[0].dist=-1; for(int i=1;i<=n;++i) scanf("%d",&a[i]),hp1.re(i,a[i]),hp2.re(i,a[i]),q.push(i); while(!q.empty()) { int a1=q.front(); q.pop(); if(q.empty()) break; int a2=q.front(); q.pop(); hp2.ROOT=hp2.Merge(a1,a2); q.push(hp2.ROOT); } int q; scanf("%d",&q); while(q--) { char s[3]; int x,y,v; scanf("%s",&s); if(s[0]=='U') { scanf("%d %d",&x,&y); Merge(x,y); } if(s[0]=='A') { if(s[1]=='1'){ scanf("%d %d",&x,&v); Addpoint(x,v); } if(s[1]=='2'){ scanf("%d %d",&x,&v); Addlink(x,v); } if(s[1]=='3'){ scanf("%d",&v); Addheap(v); } } if(s[0]=='F') { int ans=0; if(s[1]=='1'){ scanf("%d",&x); ans=Getpoint(x);} if(s[1]=='2'){ scanf("%d",&x); ans=Getlink(x); } if(s[1]=='3') ans=Getheap(); printf("%d\n",ans); } } return 0; } ```