Acmer可怜啊,根本没有休息,昨天才刚刚完成了矩阵专题,今天又要开线段树专题了。唉,等我以后月薪15K的时候,我要好好享受人生。。。。。。呃,扯远了。线段树是一个非常重要的数据结构,以前就学习过,但是没有系统的刷过难题,这次我决定将kuangbin先生的专题和NotOnlySuccess大神的专题一起刷掉。因为题目多又难,所以分成几个部分(最多三个把)。
对于线段树的话,主要是理解它的树形结构吧,推荐和树状数组一起学习。似乎树状数组就是线段树的退化,树状数组节约了差不多一半的空间,但是必须满足“加法原则”(我不清楚的,说错了别打我)。理解的话,我没有什么比较好的资料,但是模版的话,强烈推荐NotOnlySuccess大神的模版,写得非常非常好。
第一题 HDU 1166
分析:线段树的入门模版题。求区间和+点修改
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 50000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int sum[maxn<<2]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){ if(l==r){ scanf("%d",&sum[rt]); return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int l,int r,int rt,int p,int add){ if(l==r){ sum[rt]+=add; return ; } int m=(l+r)>>1; if(p<=m)update(lson,p,add); else update(rson,p,add); pushup(rt); } int query(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ return sum[rt]; } int m=(l+r)>>1; int ans=0; if(L<=m)ans+=query(lson,L,R); if(R>m)ans+=query(rson,L,R); return ans; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); for(int cas=1;cas<=t;cas++){ int n; scanf("%d",&n); build(1,n,1); printf("Case %d:\n",cas); char op[10]; while(1){ scanf("%s",op); if(op[0]=='E')break; int a,b; scanf("%d %d",&a,&b); if(op[0]=='A')update(1,n,1,a,b); else if(op[0]=='S')update(1,n,1,a,-b); else cout<<query(1,n,1,a,b)<<endl; } } return 0; }
第二题 HDU 1754
分析:和前面那道题相差不大,求区间和变成求区间最大值。+点修改
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 200000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int maxx[maxn<<2]; void pushup(int rt){ maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]); } void build(int l,int r,int rt){ if(l==r){ scanf("%d",&maxx[rt]); return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int l,int r,int rt,int p,int xx){ if(l==r){ maxx[rt]=xx; return ; } int m=(l+r)>>1; if(p<=m)update(lson,p,xx); else update(rson,p,xx); pushup(rt); } int query(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ return maxx[rt] ; } int m=(l+r)>>1; int ans=0; if(L<=m)ans=max(ans,query(lson,L,R)); if(R>m)ans=max(ans,query(rson,L,R)); return ans; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n,m; while(~scanf("%d %d",&n,&m)){ build(1,n,1); char op[10]; int a,b; while(m--){ scanf("%s %d %d",op,&a,&b); if(op[0]=='Q')cout<<query(1,n,1,a,b)<<endl; else update(1,n,1,a,b); } } return 0; }
第三题 HDU 1394
分析:这是一道比较坑的题目。这道题目的意思是,给你N个数,这N个数分别是0到N-1。数列是乱序给的,让你求出它的逆序对数。然后每次将第一个放到最后一个,一次循环,这样一共可以得到N个数列,让你求出所有数列中的最小逆序对数。
那么这道题的解题思路是先求出第一个数列的逆序对数,然后可以根据前一个数列的逆序对数和数列的第一个数循环的求出下一个数列的逆序对数。
不妨设前一个数列逆序对数为ank,数列第一个数为Ai,那么下一个逆序对数为ank - Ai +n - Ai -1,我觉得应该很好想。
然后问题来了,第一个数列的逆序对数怎么求?
这里提供两种方法:
先介绍一下不用线段树的方法,那就是暴力,对能够解决一切问题的暴力大法。直接n^2的复杂度竟然没有爆掉,可能数据有点水,强烈不推荐在比赛的时候碰运气!代码也就不贴了。
第二种当然是线段树喽,我们可以依次读入数列的数,对于每个数,查询大于这个数的区间有多少个标记,这就是相对于这个数的逆序对数,然后将这个数标记,再读取下一个数。标记的最好方法,就是将这个数相对应的存储值从0变成1,求多少个标记就是求区间和。辛亏这里正好是0到N-1,万一不连续的话,那么还需要离散化。
做完这道题,我想了想,觉得这两种方法都不好,百度了一下逆序对数的求法,有时间会开一篇文章来写。
这道题本来不是一道典型的线段树问题,但是通过转化,问题变得清晰。线段树难就难在这里,不知道怎么转化。这里是,求区间和+点更新。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 5000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int dat[maxn]; int sum[maxn<<2]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=0; return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } int query(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ return sum[rt]; } int m=(l+r)>>1; int ans=0; if(L<=m)ans+=query(lson,L,R); if(R>m)ans+=query(rson,L,R); return ans; } void update(int l,int r,int rt,int p){ if(l==r){ sum[rt]=1; return ; } int m=(l+r)>>1; if(p<=m)update(lson,p); else update(rson,p); pushup(rt); } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n; while(~scanf("%d",&n)){ build(1,n,1); int ans=0; for(int i=0;i<n;i++){ scanf("%d",&dat[i]); ans+=query(1,n,1,dat[i]+1,n); update(1,n,1,dat[i]+1); } int ret=ans; for(int i=0;i<n;i++){ ans+=n-dat[i]-dat[i]-1; ret=min(ret,ans); } cout<<ret<<endl; } return 0; }
第四题 HDU 2795
分析:题目大概为有h条长为w的木板,分别编号为1到h,一共进行n次申请,每次申请使用长度为wi的木板,每条木板可以累积使用(比如长度为10的木板,用去长度为6的木板,还剩下长度为4的木板可以使用),每次申请如果可以满足,那么给出合适木板中编号最小的,如果不可以那么输出-1。
这是单点更新里面比较典型的稍难题,update和query函数要随机应变的修改。这道题,我们维护区间的最大值,然后不断搜索满足要求的最左结点,输出即可。同时,在查询的时候进行维护更新。
线段树的写法很灵活,不是那种僵硬的模版,这道题目就可以将查询和更新结合在一起写。
注意:这道题有个小坑点,题目上写h可以到10^9,但是实际询问只有200000,那么最多也只需要用到200000条木板,根本不会用到10^9这么大的数,所以两者取个最小值,再去建树才是正解。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 200000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int w; int maxx[maxn<<2]; void pushup(int rt){ maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]); } void build(int l,int r,int rt){ if(l==r){ maxx[rt]=w; return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } int update(int l,int r,int rt,int p){ if(l==r){ maxx[rt]-=p; return l; } int m=(l+r)>>1; int tmp; if(p<=maxx[rt<<1])tmp=update(lson,p); else tmp=update(rson,p); pushup(rt); return tmp; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n,q; while(~scanf("%d %d %d",&n,&w,&q)){ n=min(n,q); build(1,n,1); while(q--){ int x; scanf("%d",&x); if(maxx[1]<x)cout<<-1<<endl; else cout<<update(1,n,1,x)<<endl;; } } return 0; }
第五题 POJ 2828
分析:题目大概是讲N个人按顺序插队(按先来后到的顺序,来了不一定排在最后,而是找个位置插进去)
身份值为Val(i)的人插进第Pos(i)的位置(位置是从0开始数,一直到N-1),那么原来在Pos(i)及其之后的人依次退后一个位置,最后输出所有人插完队的身份值序列。
这道题目也是一道较为典型的单点更新的稍难题。看懂题意之后,我们思考按照题意模拟的话,那么可能数据将不停的后移,这是我们不希望看到的。按照题目给的顺序做不是那么容易,所以我们反过来看。最后一个人要插的位置就是他最后所在的位置,就是剩余序列(全序列)里面的第Pos(N)。最后第二个人要插的位置就是除去最后一个人插的位置之后剩下的位置中的第 Pos(N-1)个空位。那么这道题,我们转化为寻找剩余序列的第K个位置,找到之后将这个位置标记,从剩余序列中删掉。
这道题也是求区间和+单点更新。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 200000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int dat[maxn]; int seq[maxn]; int sum[maxn<<2]; int anw[maxn]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=1; return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } int update(int l,int r,int rt,int p){ if(l==r){ sum[rt]=0; return l; } int m=(l+r)>>1; int tmp; if(p<=sum[rt<<1])tmp=update(lson,p); else tmp=update(rson,p-sum[rt<<1]); pushup(rt); return tmp; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n; while(~scanf("%d",&n)){ build(1,n,1); for(int i=0;i<n;i++) scanf("%d %d",&seq[i],&dat[i]); for(int i=n-1;i>=0;i--) anw[update(1,n,1,seq[i]+1)]=dat[i]; for(int i=1;i<n;i++) cout<<anw[i]<<" "; cout<<anw[n]<<endl; } return 0; }
第六题 POJ 2886
分析:题目给出N个孩子和一个初始值K,再按顺序给出每个孩子的名字和对应孩子的偏移值。首先针对初始值K,杀掉(为什么我这么残忍啊)第K个孩子并且给他1的因数个数个糖果,然后针对这个被杀的孩子的偏移值,相对于这个孩子的位置偏移这个偏移值(正数向大偏移,负数向小偏移),到下一个孩子,然后把他杀掉,给他2的因数个数个糖果(有点类似约瑟夫环),依次类推。。。。。。最后把所有孩子都杀死,问拿到最多糖果的孩子的名字和他所拿到的糖果数。
这道题,首先一个麻烦的地方就是第几个被杀的孩子得到的糖果数最多,最多又是多少?这里引进一个反素数的概念。反素数的定义自己百度把,推荐刷一下hdu-2521,上面反素数的概念也有。这里我仅给出反素数打表的代码。
int tot=0; bool isprime[maxn]; int prime[maxn]; void init(int n){ for(int i=2;i<=n;i++){ if(!isprime[i]) prime[tot++]=i; for(int j=0;prime[j]*i<=n;j++){ isprime[prime[j]*i]=true; if(i%prime[j]==0)break; } } } int dat[maxn]; int num[maxn]; int cal(int n){ int ans=1; for(int i=0;i<tot&&prime[i]<=n;i++) if(n%prime[i]==0){ int k=0; while(n%prime[i]==0){ k++; n/=prime[i]; } ans*=(k+1); } if(n>1)ans*=2; return ans; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); ofstream out; out.open("d:\\acm\\in.in"); init(1000); int maxx=0; int ant=0; for(int i=1;i<=500000;i++){ int sk=cal(i); if(sk>maxx){ cout<<i<<" "<<sk<<endl; maxx=sk; dat[ant]=i; num[ant++]=sk; } } for(int i=0;i<ant;i++) out<<dat[i]<<","; out<<endl; for(int i=0;i<ant;i++) out<<num[i]<<","; out<<endl; return 0; }
这样的话,我们就可以发现我们并不需要完整的求出N个孩子的被杀顺序,我们只要找到小于等于N的最大反素数,找到这个被杀孩子的名字,然后加上打表之后得到的反素数因子个数即可。然后,就是考虑怎么求这些孩子的被杀顺序。然后我们联想到上一题,略有不同的是上一题都是相对于第一个位置进行偏移的,但是这道题是相对于某个位置进行的偏移。但是我们可以很快的查询出每个区间的人数,就可以相当简单的将后者转化为前者,那么这道题就解决了。
这道问题是求区间和+单点更新,用到了其他知识(比如数论),有点难度。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 500000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int dat[]={1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,45360,50400,55440,83160,110880,166320,221760,277200,332640,498960,600001}; int num[]={1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,64,72,80,84,90,96,100,108,120,128,144,160,168,180,192,200}; int sum[maxn<<2]; char name[maxn][15]; int seq[maxn]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=1; return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } int update(int l,int r,int rt,int p){ if(l==r){ sum[rt]=0; return l; } int m=(l+r)>>1; int tmp; if(p<=sum[rt<<1])tmp=update(lson,p); else tmp=update(rson,p-sum[rt<<1]); pushup(rt); return tmp; } int query(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ return sum[rt]; } int m=(l+r)>>1; int ans=0; if(L<=m)ans+=query(lson,L,R); if(R>m)ans+=query(rson,L,R); return ans; } int cal(int n){ for(int i=0;;i++) if(dat[i]>n) return i-1; } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n,k; while(~scanf("%d %d",&n,&k)){ build(1,n,1); int ant=cal(n); for(int i=1;i<=n;i++) scanf("%s %d",name[i],&seq[i]); for(int i=0;i<n;i++){ int tmp=update(1,n,1,k); if(i==dat[ant]-1){ printf("%s %d\n",name[tmp],num[ant]); break; } //cout<<name[tmp]<<endl; k=seq[tmp]; if(k>0){ k%=sum[1]; if(k==0)k+=sum[1]; } else { k%=sum[1]; k=(sum[1]+1+k)%sum[1]; } int rightsum=query(1,n,1,tmp+1,n); if(k>rightsum)k-=rightsum; else k+=(sum[1]-rightsum); } } return 0; }
然后点更新就告一段落了,现在开始刷区间更新。区间更新主要是使用了懒惰思想,并不是每一段区间需要更新,那么就更新到每个点的最底层,而是在中间段进行懒惰处理,暂时标记一下。等到以后需要查询的时候,查询到哪一层那么就更新到哪一层,这样大大的加快了速度。因为说实话,区间更新有些难题,真的很难,那么这一部分专题的主要内容围绕简单的区间更新来,之后的难题会在下一部分。
第七题 HDU 1698
分析:区间更新的模版题。求区间和+区间更新。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 100000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int sum[maxn<<2]; int col[maxn<<2]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void pushdown(int rt,int m){ if(col[rt]){ col[rt<<1]=col[rt<<1|1]=col[rt]; sum[rt<<1]=(m-(m>>1))*col[rt]; sum[rt<<1|1]=(m>>1)*col[rt]; col[rt]=0; } } void build(int l,int r,int rt){ col[rt]=0; if(l==r){ sum[rt]=1; return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int l,int r,int rt,int L,int R,int z){ if(L<=l&&r<=R){ sum[rt]=(r-l+1)*z; col[rt]=z; return ; } pushdown(rt,r-l+1); int m=(l+r)>>1; if(L<=m)update(lson,L,R,z); if(R>m)update(rson,L,R,z); pushup(rt); } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); for(int cas=1;cas<=t;cas++){ int n; scanf("%d",&n); build(1,n,1); int q; scanf("%d",&q); while(q--){ int a,b,z; scanf("%d %d %d",&a,&b,&z); update(1,n,1,a,b,z); } printf("Case %d: The total value of the hook is %d.\n",cas,sum[1]); } return 0; }
第八题 POJ 3468
分析:和上一道题目基本没差。求区间和+区间更新。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 100000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- ll sum[maxn<<2]; ll col[maxn<<2]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void pushdown(int rt,int m){ if(col[rt]){ col[rt<<1]+=col[rt]; col[rt<<1|1]+=col[rt]; sum[rt<<1]+=(m-(m>>1))*col[rt]; sum[rt<<1|1]+=(m>>1)*col[rt]; col[rt]=0; } } void build(int l,int r,int rt){ if(l==r){ scanf("%lld",&sum[rt]); return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } ll query(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ return sum[rt]; } pushdown(rt,r-l+1); int m=(l+r)>>1; ll ans=0; if(L<=m)ans+=query(lson,L,R); if(R>m)ans+=query(rson,L,R); return ans; } void update(int l,int r,int rt,int L,int R,int add){ if(L<=l&&r<=R){ sum[rt]+=(r-l+1)*add; col[rt]+=add; return ; } pushdown(rt,r-l+1); int m=(l+r)>>1; if(L<=m)update(lson,L,R,add); if(R>m)update(rson,L,R,add); pushup(rt); } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n,m; while(~scanf("%d %d",&n,&m)){ build(1,n,1); char op[10]; int a,b,c; while(m--){ scanf("%s",op); if(op[0]=='Q'){ scanf("%d %d",&a,&b); cout<<query(1,n,1,a,b)<<endl; } else { scanf("%d %d %d",&a,&b,&c); update(1,n,1,a,b,c); } } } return 0; }
第九题 POJ 2528
分析:题目给你一块长度为10000000的木板贴海报,一共贴N张海报,每张告诉你海报贴的位置(即左端点和右端点),后贴的海报可以部分或者全部的遮住前面先贴的海报,问你木板上一共贴了多少张海报。(一张海报被分割成几段,那么还是算一张海报)
首先看到这道题数据量过大,一百万的线段树长度是有可能超时的加上超内存的,所以需要离散化。但是,不是简单的离散化,这里还有一个逻辑问题。举个例子,如果相对于1~10,1~4,6~10,这三张海报,是可以看见三张海报。但是如果简单离散化的话,1,4,6,10分别对应1,2,3,4,那么这三张海报离散为1~4,1~2,3~4,那么只可以看到两张海报了,那么逻辑就改变答案改变,这种离散是错误的。为了解决这种错误,我们考虑两个相邻数字间距大于1的话,那么在中间随便插入一个数字,这样就不会出现错误。
解决了离散化的问题,还需要考虑怎么解决求海报张数。那么我们以1,2,3。。。给每个海报编号,在没有贴海报的地方用0表示,在需要贴海报的区间用相应的编号去更新,最后将整条木板计算出来,hash求出海报出现的张数。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 10000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int col[maxn<<4]; int dat[maxn][2]; int hash[maxn<<1]; int anw[maxn<<2]; int vis[maxn<<2]; int erfen(int l,int r,int k){ while(l<=r){ int m=(l+r)>>1; if(hash[m]==k)return m+1; else if(hash[m]<k)l=m+1; else r=m-1; } return -1; } void build(int l,int r,int rt){ col[rt]=0; if(l==r){ return ; } int m=(l+r)>>1; build(lson); build(rson); } void pushdown(int rt){ if(col[rt]){ col[rt<<1]=col[rt<<1|1]=col[rt]; col[rt]=0; } } void update(int l,int r,int rt,int L,int R,int p){ if(L<=l&&r<=R){ col[rt]=p; return; } pushdown(rt); int m=(l+r)>>1; if(L<=m)update(lson,L,R,p); if(R>m) update(rson,L,R,p); } void query(int l,int r,int rt){ if(col[rt]){ for(int i=l;i<=r;i++) anw[i]=col[rt]; return; } if(l==r){ anw[l]=0; return; } pushdown(rt); int m=(l+r)>>1; query(lson); query(rson); } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--){ int n; scanf("%d",&n); int len=2*n; for(int i=0;i<n;i++){ scanf("%d %d",&dat[i][0],&dat[i][1]); hash[i<<1]=dat[i][0]; hash[i<<1|1]=dat[i][1]; } sort(hash,hash+len); len=unique(hash,hash+len)-hash; int tmp=len; for(int i=1;i<tmp;i++) if(hash[i]>hash[i-1]+1) hash[len++]=hash[i-1]+1; sort(hash,hash+len); build(1,len,1); for(int i=0;i<n;i++){ int a=erfen(0,len-1,dat[i][0]),b=erfen(0,len-1,dat[i][1]); update(1,len,1,a,b,i+1); } query(1,len,1); int cnt=0; clr(vis,0); for(int i=1;i<=len;i++) if(anw[i]&&vis[anw[i]]==0){ cnt++; vis[anw[i]]=1; } cout<<cnt<<endl; } return 0; }
第十题 HDU 1556
分析:这一道和上一道差不多,而且还要简单的样子。
#include <map> #include <set> #include <ctime> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define maxn 100000+10 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define pii pair<int,int> #define mp make_pair #define FI first #define SE second #define IT iterator #define PB push_back #define Times 10 typedef long long ll; typedef unsigned long long ull; typedef long double ld; const double eps = 1e-10; const double pi = acos(-1.0); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; inline void RI(int& x) { x=0; char c=getchar(); while(!((c>='0'&&c<='9')||c=='-'))c=getchar(); bool flag=1; if(c=='-') { flag=0; c=getchar(); } while(c<='9'&&c>='0') { x=x*10+c-'0'; c=getchar(); } if(!flag)x=-x; } //-------------------------------------------------- int cnt; int col[maxn<<2]; void pushdown(int rt){ if(col[rt]){ col[rt<<1]+=col[rt]; col[rt<<1|1]+=col[rt]; col[rt]=0; } } void build(int l,int r,int rt){ col[rt]=0; if(l==r){ return; } int m=(l+r)>>1; build(lson); build(rson); } void update(int l,int r,int rt,int L,int R){ if(L<=l&&r<=R){ col[rt]++; return; } pushdown(rt); int m=(l+r)>>1; if(L<=m)update(lson,L,R); if(R>m)update(rson,L,R); } void query(int l,int r,int rt){ if(l==r){ if(cnt)putchar(' '); cnt=1; printf("%d",col[rt]); return; } pushdown(rt); int m=(l+r)>>1; query(lson); query(rson); } int main(){ //freopen("d:\\acm\\in.in","r",stdin); int n; while(scanf("%d",&n),n){ build(1,n,1); for(int i=0;i<n;i++){ int a,b; scanf("%d %d",&a,&b); update(1,n,1,a,b); } cnt=0; query(1,n,1); puts(""); } return 0; }
暂时就这么多了,下一部分介绍更多线段树知识与题目。