您的位置:首页 > 其它

[BZOJ2333][SCOI2011]棘手的操作(可并堆||线段树)

2017-03-07 07:03 423 查看

=== ===

这里放传送门

=== ===

题解

这道题有两种做法:可并堆和线段树。相比于可并堆的写法来说线段树的写法非常简单并且好懂。。。然而这道题作为可并堆的练习也确实很有价值。。

首先提一句线段树的解法。可以发现它只要并到一起去的联通块是不会再拆开的,所以我们能够把每个时刻出现的连通块都标号为一个连续的区间,那么就可以用线段树进行区间修改区间查询。

操作方法是先把操作离线,然后对于每个合并操作用并查集来维护,为每个集合维护一个ed数组表示这一块的最后一个点的编号,再为每个点维护一个nxt值表示它的下一个节点编号。也就是说用并查集的father数组可以找到这个连通块的第一个节点,而ed数组可以找到最后一个,这就保证了节点的顺序。

在合并两个集合的时候按照顺序把一段节点接到另一段节点后面,这就要求在修改的时候一定要按顺序进行。并且这种操作方式决定了只有代表元素的ed值是正确的,因为每次修改的时候不能顺着并查集全改一遍不然肯定T死。

遍历所有点的时候每次遇到一个代表元素就用nxt数组遍历所有连通块然后依次标号就可以了。然后把father和ed数组初始化,重新做一边操作来处理询问就可以了。

对于可并堆来说的话就不需要离线直接在线处理就可以了。对于第一个操作就是直接合并两个堆,对于第二个操作它需要修改单个元素的值,那么这个可并堆必须支持找到某个元素的位置并且修改它,那么向上调整和向下调整两个操作都要搞出来;然后因为可并堆用指针记录了左右儿子和父亲的位置,相当于是搞了一个双向指针的东西,动了一个地方其它都跟着乱动就特烦人。。

因为第三个操作要修改整个连通块,所以要在可并堆里面维护lazy标记,每次merge操作的时候得先push一下把标记传下去。并且向上调整之前还要先把它祖先的标记都放下来。

最麻烦的操作就是整体最大值的维护。。因为编号是散乱的所以没法直接用线段树之类的东西来搞,所以就搞了个堆套堆。。似乎用STL也可以?这题ATP在WC的时候调了三天因为搞得断断续续所以也写得奇丑无比。。目测根本没法看qwq

代码

简洁明了的线段树版本

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,a[300010],father[300010],ed[300010],w[300010],num[300010],next[300010],cnt;
int Max[1500000],dlt[1500000];
struct question{
int k,x,y;
}q[300010];
char get(){
int c=getchar();
while (c>'Z'||c<'A') c=getchar();
return c;
}
int find(int x){
if (father[x]!=x) father[x]=find(father[x]);
return father[x];
}
void update(int i){
Max[i]=max(Max[i<<1],Max[(i<<1)+1]);
}
void pushdown(int i){
if (dlt[i]!=0){
Max[i<<1]+=dlt[i];Max[(i<<1)+1]+=dlt[i];
dlt[i<<1]+=dlt[i];dlt[(i<<1)+1]+=dlt[i];
dlt[i]=0;
}
}
void build(int i,int l,int r){
if (l==r){
Max[i]=num[l];return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build((i<<1)+1,mid+1,r);
update(i);
}
void change(int i,int l,int r,int left,int right,int v){
if (left<=l&&right>=r){
Max[i]+=v;dlt[i]+=v;return;
}
int mid=(l+r)>>1;
pushdown(i);
if (left<=mid) change(i<<1,l,mid,left,right,v);
if (right>mid) change((i<<1)+1,mid+1,r,left,right,v);
update(i);
}
int ask(int i,int l,int r,int left,int right){
if (left<=l&&right>=r) return Max[i];
int mid=(l+r)>>1,ans=-0x7fffffff;
pushdown(i);
if (left<=mid) ans=max(ans,ask(i<<1,l,mid,left,right));
if (right>mid) ans=max(ans,ask((i<<1)+1,mid+1,r,left,right));
return ans;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
father[i]=ed[i]=i;
}
scanf("%d",&m);
for (int i=1;i<=m;i++){
char c=get();
if (c=='U'){
int r1,r2;
q[i].k=1;scanf("%d%d",&q[i].x,&q[i].y);
r1=find(q[i].x);r2=find(q[i].y);
if (r1!=r2){//注意合并操作的顺序要求
father[r2]=r1;next[ed[r1]]=r2;ed[r1]=ed[r2];
}
}
if (c=='A'){
char z=getchar();
if (z=='3'){
q[i].k=4;scanf("%d",&q[i].x);
}else{
q[i].k=z-'0'+1;scanf("%d%d",&q[i].x,&q[i].y);
}
}
if (c=='F'){
char z=getchar();
if (z=='3') q[i].k=7;
else {q[i].k=z-'0'+4;scanf("%d",&q[i].x);}
}
}
for (int i=1;i<=n;i++)
if (find(i)==i){
for (int j=i;j!=0;j=next[j]){
w[j]=++cnt;num[cnt]=a[j];
}
}
for (int i=1;i<=n;i++) father[i]=ed[i]=i;
build(1,1,n);
for (int i=1;i<=m;i++)
switch (q[i].k){
case 1:{
int r1,r2;
r1=find(q[i].x);r2=find(q[i].y);
if (r1!=r2){
father[r2]=r1;next[ed[r1]]=r2;ed[r1]=ed[r2];
}//重新进行一遍操作
break;
}
case 2:{
change(1,1,n,w[q[i].x],w[q[i].x],q[i].y);
break;
}
case 3:{
int r=find(q[i].x);
change(1,1,n,w[r],w[ed[r]],q[i].y);
break;
}
case 4:{
change(1,1,n,1,n,q[i].x);
break;
}
case 5:{
printf("%d\n",ask(1,1,n,w[q[i].x],w[q[i].x]));
break;
}
case 6:{
int r=find(q[i].x);
printf("%d\n",ask(1,1,n,w[r],w[ed[r]]));
break;
}
case 7:{
printf("%d\n",ask(1,1,n,1,n));
break;
}
}
return 0;
}


丑陋至极的可并堆版本

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,q,a[300010],delta,father[300010],ptr[300010];
struct Node{
Node *l,*r,*fa;
int val,NPL,dlt;
Node();
Node(int x);
void count(){NPL=r->NPL+1;}
void push();
void Add(int v);
void pushdlt();
void faswap();
void pushup(int rt);
void pushdown(int rt);
}*null,H[300010],*h[300010],*st[300010];
struct Temp{
Node* *p;
int id;
}tmp[300010];
Node::Node(){l=r=fa=null;val=NPL=dlt=0;}
Node::Node(int x){l=r=fa=null;val=x;NPL=1;dlt=0;}
void Node::Add(int v){val+=v;dlt+=v;}
void Node::push(){
if (dlt!=0){
if (l!=null) l->Add(dlt);
if (r!=null) r->Add(dlt);
dlt=0;
}
}
void Node::pushdlt(){
Node *ptr=this;
int top=0;
while (ptr!=null){
st[++top]=ptr;
ptr=ptr->fa;
}
for (int i=top;i>=1;i--) st[i]->push();
}
void Node::faswap(){
Node *k=fa;
if (this==k->l){
swap(r,k->r);k->l=l;l=k;
}else{swap(l,k->l);k->r=r;r=k;}
fa=k->fa;
if (k==k->fa->l) k->fa->l=this;
else k->fa->r=this;
if (k->r!=null) k->r->fa=k;
if (k->l!=null) k->l->fa=k;
if (l!=null) l->fa=this;
if (r!=null) r->fa=this;
count();k->count();
}
void Node::pushup(int rt){
Node *k=fa;
if (k==null){h[rt]=this;return;}
if (val<=k->val) return;
faswap();pushup(rt);
}
void Node::pushdown(int rt){
int Maxs=-1;
Node *tmp;
if (this==null||(l==null&&r==null)) return;
Maxs=max(l->val,r->val);
l->push();r->push();
if (Maxs<=val) return;
if (Maxs==l->val) l->faswap();
else r->faswap();
if (fa->fa==null) h[rt]=fa;
pushdown(rt);
}
Node* merge(Node *x,Node *y){
if (x==null) return y;
if (y==null) return x;
if (x->val<y->val) swap(x,y);
x->push();
x->r=merge(x->r,y);
x->r->fa=x;
if (x->l->NPL<x->r->NPL) swap(x->l,x->r);
x->count();return x;
}
int comp(Temp x,Temp y){return (*(x.p))->val>(*(y.p))->val;}
int find(int x){
if (father[x]==x) return father[x];
father[x]=find(father[x]);
return father[x];
}
int getnum(char x,char y){
int c;
if (x=='U') return 1;
if (x=='A') c=1;
else c=4;
return c+y-'0';
}
void pushup(int now){
int fa=now>>1;
if (fa==0||(*tmp[now].p)->val<=(*tmp[fa].p)->val) return;
tmp[now].id=find(tmp[now].id);
tmp[fa].id=find(tmp[fa].id);
if (tmp[now].id!=0&&tmp[fa].id!=0)
swap(ptr[tmp[now].id],ptr[tmp[fa].id]);
swap(tmp[now],tmp[fa]);
pushup(fa);
}
void pushdown(int now){
int Maxs=-0x7fffffff,l=now<<1,r=l+1,r1,r2,r3;
if (l>n&&r>n) return;
if (l<=n) Maxs=max(Maxs,(*tmp[l].p)->val);
if (r<=n) Maxs=max(Maxs,(*tmp[r].p)->val);
tmp[now].id=r1=find(tmp[now].id);
tmp[l].id=r2=find(tmp[l].id);
tmp[r].id=r3=find(tmp[r].id);
if (r2==0&&r3==0) return;
if (Maxs<=(*tmp[now].p)->val||r1==0) return;
if (r2!=0&&Maxs==(*tmp[l].p)->val){
swap(ptr[r2],ptr[r1]);
swap(tmp[l],tmp[now]);
pushdown(l);
}else if (r3!=0){
swap(ptr[tmp[r].id],ptr[tmp[now].id]);
swap(tmp[r],tmp[now]);
pushdown(r);
}
}
void Union(int x,int y){
int r1=find(x),r2=find(y);
if (r1==r2) return;
tmp[ptr[r2]].p=&null;
pushdown(ptr[r2]);
tmp[ptr[r2]].id=ptr[r2]=0;
father[r2]=r1;
h[r1]=merge(h[r1],h[r2]);
pushup(ptr[r1]);
}
void Addpoint(int x,int v){
int rt=find(x);
H[x].pushdlt();//下放当前节点的标记
H[x].val+=v;
if (v>0) H[x].pushup(rt);
else H[x].pushdown(rt);//调整节点在当前堆里的位置
if (v<0) pushdown(ptr[rt]);//调整当前堆顶的位置
else pushup(ptr[rt]);
}
void Addblock(int x,int v){
int rt=find(x);
h[rt]->Add(v);
if (v<0) pushdown(ptr[rt]);
else pushup(ptr[rt]);
}
int main()
{
null=new Node;*null=Node();
null->val=-0x7fffffff;
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
father[i]=i;H[i]=Node(a[i]);
h[i]=H+i;tmp[i].p=h+i;
tmp[i].id=i;
}
sort(tmp+1,tmp+n+1,comp);
for (int i=1;i<=n;i++) ptr[tmp[i].id]=i;
scanf("%d",&q);
for (int i=1;i<=q;i++){
char s[5];scanf("%s",s);
int opt,x,y,v,rt;
opt=getnum(s[0],s[1]);
switch (opt){
case 1:{
scanf("%d%d",&x,&y);
Union(x,y);break;
}
case 2:{
scanf("%d%d",&x,&v);
Addpoint(x,v);break;
}
case 3:{
scanf("%d%d",&x,&v);
Addblock(x,v);break;
}
case 4:{
scanf("%d",&v);
delta+=v;break;
}
case 5:{
scanf("%d",&x);H[x].pushdlt();
printf("%d\n",H[x].val+delta);break;
}
case 6:{
scanf("%d",&x);rt=find(x);
printf("%d\n",h[rt]->val+delta);break;
}
case 7:{printf("%d\n",(*tmp[1].p)->val+delta);break;}
}
}
return 0;
}


偏偏在最后出现的补充说明

这题的线段树做法是很经典的思路啊

可并堆的做法就适合闲着没事的时候花上一天磨磨蹭蹭写着玩啊
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息