毕竟是写给自己看的
还是写好看一点吧
一、
传送门 HDOJ 1754
题意~
给出N个数,两种操作:
1、U x y:修改第x个数的值为y;
2、Q x y:求第x到第y个的最大值,注:x未必比y小
我们可以理解成总裁管左右两个总经理,总经理管左右两个副总经理,副总经理管左右两个经理……每个管理者都只知道自己的值,不知道他们手下的值,所以他们每次都要问自己的手下,而他们的手下每次更新的时候都要反馈给上司……这样就很好理解了
#include
#include
using namespace std;
int a[210000];//员工,只负责一开始表达自己的值
struct node //管理者(假设它是经理),管理者绝对不是员工,一定要分开来
//最底层的管理者只管一个员工,绝对不能想着:反正一个员工,就自己管自己算了。
//理解:有N个员工,就配有2N-1个管理者,用空间换时间
{
int l,r,lc,rc,c;// 管理的员工编号是第l个到第r个(连续),lc表示左副经理的编号,rc表示右副经理是谁
//c表示第l个员工至第r个员工的特征值:可以是和、最大值、或者最小值
}tr[410000]; int len;//tr数组就是管理者数组,len表示当前申请到第几个管理者
//到此,员工有自己的编号,管理者也有自己的编号
int mymax(int x,int y){ return x>y?x:y;} //求x和y最大值的函数
void bt(int l,int r) // build tree建立线段树,申请一个管理者,管理第l个员工至第r个员工
{
len++; int now=len;// now记录当前管理者的编号
tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1;tr[now].c=0;//一开始当前管理者now的左右副总经理都是没人-1,管理者管理范围最大值为0,这里五个元素都要赋值
if(l1人,就有权申请两个副总经理帮忙管人
{
int mid=(l+r)/2; // mid为l和r的中间值,从中间分为两段[l,mid]和[mid+1,r]
tr[now].lc=len+1; bt( l , mid ); // [l,mid]给左副总管,让他先去管好[l,mid]
tr[now].rc=len+1; bt( mid+1 , r ); // [mid+1,r]给右副总管,让他先去管好[mid+1,r]
}
}
void change(int now,int x,int k)//change的功能:在当前管理者now的管理范围内,把管第x个员工的管理者(不知道该管理者的编号)的值改为k
//理解:为什么第x个一定在now的管理范围内呢?
//注意:修改,改的是管理者,不是改员工,员工已经没用了。
{
if( tr[now].l==tr[now].r) { tr[now].c=k;return ;}//如果now只管一人,那么这个人的编号一定是x,为什么?
int lc= tr[now].lc, rc=tr[now].rc;//找出now的左右副总分别是谁
int mid=( tr[now].l+ tr[now].r)/2;//找到now管理范围的中间位置
if( x<=mid) change(lc,x,k); //如果x在now的左副总的管理范围,那么修改这件事就交给左副总去做
else if( mid+1<=x) change(rc,x,k); //如果x在now的右副总的管理范围,那么修改这件事就交给右副总去做
tr[now].c= mymax( tr[ lc ] .c , tr[ rc ].c );//修改完后,注意要维护,有可能最大值发生变化了
}
int findmax(int now,int l,int r)//findmax的功能:在当前管理者now的管理范围内,找出第l个员工至第r个员工的最大值
{
if( l== tr[now].l && tr[now].r== r) return tr[now].c;//如果now的管理范围刚好是[l,r],就不用问左右副总了
int lc= tr[now].lc, rc=tr[now].rc;
int mid=( tr[now].l+ tr[now].r)/2;
if( r<=mid) return findmax(lc,l,r); //[l,r]在左副总的管理范围内
else if( mid+1<=l) return findmax(rc,l,r); //[l,r]在右副总的管理范围内
else return mymax( findmax(lc,l,mid) , findmax(rc,mid+1,r) );//其他情况就是[l,r]一部分在左副总,一部分在右副总
}
int main()
{
int n,m,i,x,y; char ss[10];
while( scanf("%d%d",&n,&m)!=EOF)
{
for(i=1;i<=n;i++) scanf("%d",&a[i]);
len=0; bt(1,n);tr[1].c=0; //初始化len为0,一开始没有一个管理者
for(i=1;i<=n;i++) change(1,i,a[i]);//初始化,把第i个位置改为a[i]
for(i=1;i<=m;i++)
{
scanf("%s%d%d",ss,&x,&y);
if(ss[0]=='Q') printf("%d\n", findmax(1, x,y) );
else change( 1, x, y);
}
}
return 0;
}
虽然用线段树容易空间(时间)超限,值得庆幸的是这道题数据比较弱啊,不会超限
传送门_poj2777
题目大概是这样的:
有L段线段(编号为1~L) ,一开始 全部是颜色1。有两种操作
1、C A B tt :A~B染第tt种颜色
2、P A B :询问A~B有多少种不一样的颜色。
还是要注意A有可能比B大。
这道题每次更改的是一段数值,并且是求出一段距离的颜色数量,所以这道题体现线段树更彻底。而更新次数就比上一道题大很多了,所以我们在更新时极有可能会更新超时。所以我们这里会用到lazy算法。
所谓lazy算法就是在更新时,只更新要求更新的这一段,而不更新他的手(儿)下(子)们。当我们要访问他的这个手(儿)下(子)时,我们才去更新他的值。这样我们就能减少很多更新的次数,从而减少时间。
然而这道题我有两个不同版本的代码
先看看第一个:
#include
#include
using namespace std;
struct node
{
int l,r,lc,rc,c;
}tr[210000]; int len;
bool v[35];
void bt(int l,int r) // build tree
{
len++; int now=len;
tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1;
if(lint mid=(l+r)/2;
tr[now].lc=len+1;bt(l,mid);
tr[now].rc=len+1; bt(mid+1,r);
}
}
void wen(int now,int l,int r)//wen(问)函数的功能:把now所管理范围中第l个员工至第r个员工的颜色都在v数组里面体现为true
{
if( tr[now].c>0) { v[tr[now].c]=true; return ;}// tr[now].c>0表示now管理范围的颜色是统一的,那么就不用麻烦左右副总了
int lc= tr[now].lc, rc=tr[now].rc;
int mid=( tr[now].l+ tr[now].r)/2;
if( r<=mid) wen(lc,l,r); //[l,r]在左副总的管理范围,这件事情就交给左副总去做
else if( mid+1<=l) wen(rc,l,r); //[l,r]在右副总的管理范围,这件事情就交给右副总去做
else //来到了这个else就是表示[l,r]有一部分在左副总,有一部分在右副总
{
wen(lc, l , mid );
wen(rc, mid+1 , r );
}
}
void change(int now,int l,int r,int k)//change(改)函数的功能 :在now的管理范围内,把[l,r]改为第k种颜色
{
if( tr[now].c==k) return ;//如果now管理的范围颜色统一,并且本来就是k,那么什么都不要做
if( tr[now].l==l&& r==tr[now].r) { tr[now].c=k;return ;} //如果刚好now的管理范围就是[l,r],那么不管now管理范围原来是什么颜色统一改就行
int lc= tr[now].lc, rc=tr[now].rc;
int mid=( tr[now].l+ tr[now].r)/2;
if(tr[now].c>0)//如果原来now的管理范围颜色统一,那么现在要改now的管理范围中的部分范围的颜色了
//此时now就想:我的把我原来的颜色先传给我的左右副总,因为他们还不知道他们现在的颜色(在这次改之前的颜色)
{ //这个步骤我们称为:继承
tr[lc].c= tr[now].c;
tr[rc].c= tr[now].c;
}
if( r<=mid) change(lc,l,r,k); //如果[l,r]在左副总的管理范围中,那么这件事情就交给左副总
else if( mid+1<=l) change(rc,l,r,k); //如果[l,r]在右副总的管理范围中,那么这件事情就交给右副总
else
{
change(lc, l , mid , k );
change(rc, mid+1 , r , k );
}
//注意:有 修改 就配带有 维护
if( tr[lc].c==tr[rc].c&& tr[lc].c>0 )tr[now].c= tr[ lc ] .c ;
else tr[now].c=-1;
}
int main()
{
int k,j,i,x,y,L,t,M,ans; char ss[10];
while( scanf("%d%d%d",&L,&t,&M)!=EOF)
{
len=0; bt(1,L);tr[1].c=1;//初始化所有颜色都为1
for(i=1;i<=M;i++)
{
scanf("%s",ss);
if(ss[0]=='C')
{
scanf("%d%d%d",&x,&y,&k);
if( x>y) { int tt=x;x=y;y=tt;}
change(1,x,y,k);
}
else
{
scanf("%d%d",&x,&y); if(x>y) { int tt=x;x=y;y=tt; } //这是个坑
memset(v,false,sizeof(v)); //一开始所有颜色都没有出现过
wen(1,x,y);
ans=0; for(j=1;j<=t;j++) if( v[j]==true) ans++;
printf("%d\n",ans);
}
}
}
return 0;
}
相信大多数人接触的都是以上这种版本。
据说这个版本在遇到一些数据时会有bug
下面推荐另外一个版本:
通过二进制的方式来记录有多少种颜色
每次更新时只要左移c-1位就可以了
在统计时就只用查找有多少个1就可以了
那!么!
如果我们用了二进制,在更新他的上(父)司(亲)时有多少种颜色是就不能用max了
我们怎么写呢???
我们知道在最后时是统计他有多少个1
并且如果他任意一个儿子有这种颜色,即使他的另外一个儿子没有,他自己也有这种颜色
而或(|)运算正好可以满足!
推荐并提供我的代码:
#include
#include
using namespace std;
int len,a[100010];
struct node
{
int lc,rc,l,r,c;bool update;
//update:false为已更新了他的儿子,true为还未更新他的儿子
//c:是颜色的状态压缩值,用二进制位表示是否选用了某种颜色
}tr[200010];
int br(int l,int r)
{
len++;int now=len;
tr[now].l=l;tr[now].r=r;tr[now].c=1;//与上题不一样的:由于开始整条都是颜色1,所以状态值处置设置为1,参考二进制的表示
tr[now].update=false;
if (lint mid=(l+r)>>1;
tr[now].lc=br(l,mid);
tr[now].rc=br(mid+1,r);
}
return now;
}
void swap(int &a,int &b)
{
int t=a;a=b;b=t;
}
int col(int x)//统计x状态二进制位1的个数
{
int ans=0;
while (x>0)
{
ans+=x%2;
x=x/2;
}
return ans;
}
void update(int x)//更新他的儿子。注意这一步一定要在访问他的儿子之前做好
{
tr[x].update=false;
tr[tr[x].lc].update=true;
tr[tr[x].lc].c=tr[x].c;
tr[tr[x].rc].update=true;
tr[tr[x].rc].c=tr[x].c;
}
void change(int x,int l,int r,int c)
//返回值为该线段的编号
{
if (tr[x].l==l&&tr[x].r==r)
{
tr[x].c=1<<(c-1);
//标记状态值二进制右起第c位为1,其他为0
//若整段匹配,则更新该线段update标记
return ;
}
int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc;
if (tr[x].update) update(x);//在访问儿子之前必须更新儿子,切记!!也是该算法的重点。
if (r<=mid) change(lc,l,r,c);
else if (l>=mid+1) change(rc,l,r,c);
else {change(lc,l,mid,c);change(rc,mid+1,r,c);}
tr[x].c=(tr[lc].c|tr[rc].c);//合并左右儿子的状态。使用“或”操作计算
}
int findmax(int x,int l,int r)
{
if (tr[x].l==l&&tr[x].r==r) return tr[x].c;
int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc;
if (tr[x].update) update(x); //在访问儿子之前必须更新儿子
if (l>=mid+1) return findmax(rc,l,r);
else if (r<=mid) return findmax(lc,l,r);
else return (findmax(lc,l,mid)|findmax(rc,mid+1,r));//返回左右儿子合并值
}
int main()
{
int n,m,t,x,y,z;
char c;
scanf("%d%d%d",&n,&t,&m);
br(1,n);len=0;
for (int i=1;i<=m;i++)
{
getchar();
scanf("%c",&c);
if (c=='C')
{
scanf("%d%d%d",&x,&y,&z);
if (x>y) swap(x,y);
change(1,x,y,z);
}
if (c=='P')
{
scanf("%d%d",&x,&y);
if (x>y) swap(x,y);//本题坑点之一,必须注意大小关系
printf("%d\n",col(findmax(1,x,y)));
}
}
return 0;
}
上面讲了优化时间的lazy算法,下面来讲讲优化空间的
。
所谓离散化就是
原数组为A[]={3,100,9845587},
这个数组那么大,但是却只用到了很少一部分
而 {1~2,4~99,…}都没有用到
我们怎样能高效地利用这些空间呢
那么我们就要用到离散化 了
如上述的A离散化为S[]={1,2,3}
再比如:
原数组为A[]={ 3 , 100 , 9845587 , 6 , 6 , 9 , 11 , 2 },
离散化为S[]={ 2 , 6 , 7 , 3 , 3 , 4 , 5 , 1 }
离散化的过程:
1:原数组A每个数还得多带点东西:头x(原来的值),胸口p(原来的位置),肚子z(离散化的值)
2:把A复制一份为B,然后B数组根据B中x的值进行排序,然后根据B排序后的位置赋予每个B的离散值
3:让B中的每个元素带着自己的离散值和位置值去赋值A数组中的z值。A[ B[i].p ] = B[i].z ;
4:到此离散化结束,A.z就是A.x的离散值,而且一一对应。
代码:
#include
#include
#include
struct node{int x,y,p;};//离散化用(意思同上述)
node a[20010],b[20010];
int n,mm;
struct tree{int l,r,lc,rc,c;bool update;
//update是否有更新儿子(lazy),真需要更新假已更新};
tree tr[40010];
int len=0;
bool col[20010];
void qsort(int l,int r)//快排
{
int i=l,j=r;
node mid,t;
mid=b[(l+r)>>1];
while (i<=j)
{
while (b[i].xx) i++;
while (b[j].x>mid.x) j--;
if (i<=j)
{
t=b[i];b[i]=b[j];b[j]=t;
i++;j--;
}
}
if (lif (iint bt(int l,int r)
{
len++;
int now=len;
tr[now].l=l;
tr[now].r=r;
tr[now].c=0;
tr[now].lc=-1;
tr[now].rc=-1;
tr[now].update=false;
if (l+1!=r)
{
int mid;
mid=(l+r)>>1;
tr[now].lc=bt(l,mid);
tr[now].rc=bt(mid,r);
}
return now;
}
void update(int x)//lazy算法
{
tr[x].update=false;
if (tr[x].lc!=-1 && tr[x].rc!=-1)
{
tr[tr[x].lc].update=tr[tr[x].rc].update=true;
tr[tr[x].lc].c=tr[tr[x].rc].c=tr[x].c;
}
}
void insert(int x,int l,int r,int p)//change
{
if (tr[x].l==l && tr[x].r==r)
{
tr[x].c=p;
tr[x].update=true;
return;
}
if (tr[x].update) update(x);
int mid;
mid=(tr[x].l+tr[x].r)>>1;
if (r<=mid) insert(tr[x].lc,l,r,p);
else if (l>=mid) insert(tr[x].rc,l,r,p);
else {insert(tr[x].lc,l,mid,p);insert(tr[x].rc,mid,r,p);};
}
int main()
{
int m;
scanf("%d",&m);
for (int u=1;u<=m;u++)
{
scanf("%d",&n);n=n<<1;
for (int i=1;i<=n;i+=2)
{
scanf("%d %d",&a[i].x,&a[i+1].x);
a[i+1].x++;
a[i].p=i;
a[i+1].p=i+1;
//记录
}
for (int i=1;i<=n;i++)
b[i]=a[i];
qsort(1,n);
b[1].y=1;
for (int i=2;i<=n;i++)
if (b[i].x==b[i-1].x) b[i].y=b[i-1].y;
else b[i].y=b[i-1].y+1;//离散化
for (int i=1;i<=n;i++)
{
a[b[i].p].y=b[i].y;//记录位置
}
len=0;
bt(1,b[n].y);
for (int i=1;i<=n;i+=2)
{
insert(1,a[i].y,a[i+1].y,i);
}
memset(col,false,sizeof(col));
for (int i=1;i<=len;i++)
{
if (tr[i].update) update(i);
if (tr[i].lc==-1) col[tr[i].c]=true;//统计
}
int ans=0;
for (int i=1;i<=n;i++)
if (col[i]) ans++;
printf("%d\n",ans);
}
}