CODEVS1369 xth 砍树
题目大意:区间查询和,单点修改区间中点。
思路:比较简单的线段树,可是在double和float上栽了跟头,以后统一用double,输出printf里面用f,不要用llf(我zuo)。
#include<iostream> #include<cstdio> using namespace std; int tree[800000]={0},a[200001]={0}; void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=a[l]; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=tree[i*2]+tree[i*2+1]; } int work(int i,int l,int r,int aa,int b) { int mid,sum=0; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) sum+=work(i*2,l,mid,aa,b); if (b>mid) sum+=work(i*2+1,mid+1,r,aa,b); return sum; } void insert(int i,int l,int r,int aa) { int mid; if (l==r&&l==aa) { tree[i]=0; return; } mid=(l+r)/2; if (aa<=mid) insert(i*2,l,mid,aa); else insert(i*2+1,mid+1,r,aa); tree[i]=tree[i*2]+tree[i*2+1]; } int main() { int n,m,i,j,l,r,sum; double ans; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); build(1,1,n); scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d",&l,&r); sum=work(1,1,n,l,r); insert(1,1,n,(l+r)/2); ans=sum*3.14; printf("%0.2f\n",ans); } }
CODEVS1690 开关灯
题目大意:区间置反,区间查询。
思路:用delta数组表示区间置反的状态,这里就有出现了一个小问题,对于delta数组的思想不够了解,认识不够深入,其实delta数组表示的是这个节点的状态的累加,在每次pushdown的时候会将这个状态推至孩子,自己的状态清为0(大多数),所以在这个程序的paint中,delta[i]=!delta[i],而不是delta[i]=0。以后应该好好理解最基本的东西,注意细节。
#include<iostream> #include<cstdio> using namespace std; int tree[400000]={0},delta[400000]={0}; void paint(int i,int l,int r) { tree[i]=r-l+1-tree[i]; delta[i]=!delta[i]; } void pushdown(int i,int l,int r) { int mid; mid=(l+r)/2; paint(i*2,l,mid); paint(i*2+1,mid+1,r); delta[i]=0; } void insert(int i,int l,int r,int a,int b) { int mid; if (a<=l&&r<=b) { paint(i,l,r); return; } if (delta[i]==1) pushdown(i,l,r); mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,b); if (b>mid) insert(i*2+1,mid+1,r,a,b); tree[i]=tree[i*2]+tree[i*2+1]; } int work(int i,int l,int r,int a,int b) { int mid,sum=0; if (a<=l&&r<=b) return tree[i]; if (delta[i]==1) pushdown(i,l,r); mid=(l+r)/2; if (a<=mid) sum+=work(i*2,l,mid,a,b); if (b>mid) sum+=work(i*2+1,mid+1,r,a,b); return sum; } int main() { int n,m,i,j,l,r,ans; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&j,&l,&r); if (j==0) insert(1,1,n,l,r); else { ans=work(1,1,n,l,r); printf("%d\n",ans); } } }
CODEVS1228 苹果树
在卡卡的房子外面,有一棵苹果树。每年的春天,树上总会结出很多的苹果。卡卡非常喜欢吃苹果,所以他一直都精心的呵护这棵苹果树。我们知道树是有很多分叉点的,苹果会长在枝条的分叉点上面,且不会有两个苹果结在一起。卡卡很想知道一个分叉点所代表的子树上所结的苹果的数目,以便研究苹果树哪些枝条的结果能力比较强。
卡卡所知道的是,每隔一些时间,某些分叉点上会结出一些苹果,但是卡卡所不知道的是,总会有一些调皮的小孩来树上摘走一些苹果。
于是我们定义两种操作:
C x |
表示编号为x的分叉点的状态被改变(原来有苹果的话,就被摘掉,原来没有的话,就结出一个苹果) |
G x |
查询编号为x的分叉点所代表的子树中有多少个苹果 |
我们假定一开始的时候,树上全都是苹果,也包括作为根结点的分叉1。
思路:先用深搜建树(noip血淋淋的教训),给树的结点重新编号(因为是深搜的,所以一个节点孩子的编号是连续的),用新编的号做线段树,单点修改区间查询求和。
#include<iostream> #include<cstdio> using namespace std; struct use{ int ll,rr,bb; }tree1[100001]; int tree[400001]={0},bd[100001]={0},tot=0,next[200001]={0},point[100001]={0},en[200001]={0}; bool use[100001]={false}; void dfs(int i) { int y,t; use[i]=true; ++tot;t=tot; tree1[t].bb=i; bd[i]=t; y=point[i]; if (y!=0) tree1[t].ll=tot; while (y!=0) { if (!use[en[y]]) dfs(en[y]); y=next[y]; } tree1[t].rr=tot; } void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=1; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=tree[i*2]+tree[i*2+1]; } void insert(int i,int l,int r,int a) { int mid; if (l==r&&l==a) { tree[i]=1-tree[i]; return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a); else insert(i*2+1,mid+1,r,a); tree[i]=tree[i*2]+tree[i*2+1]; } int work(int i,int l,int r,int a,int b) { int mid,sum=0; if (a<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (a<=mid) sum+=work(i*2,l,mid,a,b); if (b>mid) sum+=work(i*2+1,mid+1,r,a,b); return sum; } int main() { int i,j,n,m,u,v,x,nn=0,ans; char ch; scanf("%d",&n); for (i=1;i<n;++i) { scanf("%d%d",&u,&v); ++nn; next[nn]=point[u]; point[u]=nn; en[nn]=v; ++nn; next[nn]=point[v]; point[v]=nn; en[nn]=u; } dfs(1); build(1,1,n); scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%*c%*c%c%d",&ch,&x); if (ch=='C') insert(1,1,n,bd[x]); else { ans=work(1,1,n,tree1[bd[x]].ll,tree1[bd[x]].rr); printf("%d\n",ans); } } }
一开始被坑了。。。rc!!!10*rc。。。原来是数据之间有换行。。。换行!!!
TYVJ1305最大子序和
题目大意:求长度不超过m的最大连续子序列的和。
思路:前缀和数组,然后以i为结尾的最大和等于sum[i]-min(sum[i-j])(0<=j<=m)。注意tree中保存的是sum数组的最小值,不要忘了把sum[0]加入进去,一开始wa了一个点。。。
#include<iostream> #include<cstdio> using namespace std; int tree[1200000]={0},a[300001]={0},sum[300001]={0}; void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=sum[l]; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=min(tree[i*2],tree[i*2+1]); } int work(int i,int l,int r,int aa,int b) { int mid,summ; summ=2100000000; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) summ=min(summ,work(i*2,l,mid,aa,b)); if (b>mid) summ=min(summ,work(i*2+1,mid+1,r,aa,b)); return summ; } int main() { int i,j,n,m,ans,summ; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { scanf("%d",&a[i]); sum[i]=sum[i-1]+a[i]; } build(1,0,n); ans=0; for (i=1;i<=n;++i) { summ=work(1,0,n,max(0,i-m),i); if (sum[i]-summ>ans) ans=sum[i]-summ; } printf("%d",ans); }
CODEVS2492 上帝造题的七分钟2
XLk觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。
"第一分钟,X说,要有数列,于是便给定了一个正整数数列。
第二分钟,L说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。
第三分钟,k说,要能查询,于是便有了求一段数的和的操作。
第四分钟,彩虹喵说,要是noip难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过64位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; long long tree[400000]={0},a[100001]={0}; bool tree1[400000]={false}; void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=a[l]; if (a[l]<=1) tree1[i]=true; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=tree[i*2]+tree[i*2+1]; } void updata(int i,int l,int r) { int mid; if (tree1[i]) return; if (l==r) { tree[i]=floor(sqrt(tree[i])); if (tree[i]<=1) tree1[i]=true; return; } mid=(l+r)/2; updata(i*2,l,mid); updata(i*2+1,mid+1,r); tree[i]=tree[i*2]+tree[i*2+1]; if (tree1[i*2]&&tree1[i*2+1]) tree1[i]=true; } void insert(int i,int l,int r,int aa,int b) { int mid; if (aa<=l&&r<=b) { updata(i,l,r); return; } mid=(l+r)/2; if (aa<=mid) insert(i*2,l,mid,aa,b); if (b>mid) insert(i*2+1,mid+1,r,aa,b); tree[i]=tree[i*2]+tree[i*2+1]; if (tree1[i*2]&&tree1[i*2+1]) tree1[i]=true; } long long work(int i,int l,int r,int aa,int b) { int mid; long long sum=0; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) sum+=work(i*2,l,mid,aa,b); if (b>mid) sum+=work(i*2+1,mid+1,r,aa,b); return sum; } int main() { int n,m,i,j,x,y,t; long long ans; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lld",&a[i]); build(1,1,n); scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&t,&x,&y); if (x>y) { j=x;x=y;y=j; } if (t==0) insert(1,1,n,x,y); else { ans=work(1,1,n,x,y); printf("%lld\n",ans); } } }
codevs3243区间翻转
给出N个数,要求做M次区间翻转(如1 2 3 4变成4 3 2 1),求出最后的序列。
思路:因为这里的数据范围有特殊意义,交换的区间一定是线段树上的一个结点,所以就很简单了。但是,和我平时写的线段树不同,需要保存叶节点的位置,和每个节点的左右孩子节点的编号,用delta数组。
#include<iostream> #include<cstdio> using namespace std; int a[150001]={0},tree[600000]={0},ls[600000]={0},rs[600000]={0}; bool delta[600000]={false}; void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=l; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); ls[i]=i*2;rs[i]=i*2+1; } void paint(int i) { int t; delta[i]=!delta[i]; t=ls[i];ls[i]=rs[i];rs[i]=t; } void pushdown(int i) { paint(ls[i]); paint(rs[i]); delta[i]=false; } void insert(int i,int l,int r,int aa,int b) { int mid; if (l==aa&&r==b) { paint(i); return; } mid=(l+r)/2; if (delta[i]) pushdown(i); if (b<=mid) insert(ls[i],l,mid,aa,b); else insert(rs[i],mid+1,r,aa,b); } void print(int i) { if (tree[i]!=0) { printf("%d ",a[tree[i]]); return; } if (delta[i]) pushdown(i); print(ls[i]); print(rs[i]); } int main() { int n,m,i,j,aa,b; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); build(1,1,n); scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d",&aa,&b); insert(1,1,n,aa,b); } print(1); }
cogs1272行星序列
题目大意:
“神州“载人飞船的发射成功让小可可非常激动,他立志长大后要成为一名宇航员假期一始,他就报名参加了“小小宇航员夏令营”,在这里小可可不仅学到了丰富的宇航知识,还参与解决了一些模拟飞行中发现的问题,今天指导老师交给他一个任务,在这次模拟飞行的路线上有N个行星,暂且称它们为一个行星序列,并将他们从1至n标号,在宇宙未知力量的作用下这N个行星的质量是不断变化的,所以他们对飞船产生的引力也会不断变化,小可可的任务就是在飞行途中计算这个行星序列中某段行星的质量和,以便能及时修正飞船的飞行线路,最终到达目的地,行星序列质量变化有两种形式:
1,行星序列中某一段行星的质量全部乘以一个值
2,行星序列中某一段行星的质量全部加上一个值
由于行星的质量和很大,所以求出某段行星的质量和后只要输出这个值模P的结果即可,小可可被这个任务难住了,聪明的你能够帮他完成这个任务吗?
思路:这个题目有些不同,一开始以为只要做两套pushdown就可以了,可是后来发现操作的顺序对于结果有影响,先乘后加和先加后乘是不同的。所以我们在这里定义一个新运算:*m+c。delta存两个量,加的和乘的,更新delta,对于乘的就是直接乘,加就是先乘后加。(具体的见代码)注意中间的longlong。
这次又出现一个神错:读入的longlong变量c一开始没清零,结果结果出现了负数。。。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; long long tree[400000]={0},delta[400000][2]={0},a[100001]={0},p; void build(int i,int l,int r) { int mid; if (l==r) { tree[i]=a[l]%p; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=(tree[i*2]+tree[i*2+1])%p; } void paint(int i,int l,int r,long long m,long long c) { tree[i]=((tree[i]*m)%p+(r-l+1)*c)%p; delta[i][0]=(delta[i][0]*m)%p; delta[i][1]=((delta[i][1]*m)%p+c)%p; } void pushdown(int i,int l,int r) { int mid; mid=(l+r)/2; paint(i*2,l,mid,delta[i][0],delta[i][1]); paint(i*2+1,mid+1,r,delta[i][0],delta[i][1]); delta[i][0]=1;delta[i][1]=0; } void insert(int i,int l,int r,int aa,int b,long long m,long long c) { int mid; if (aa<=l&&r<=b) { paint(i,l,r,m,c); return; } pushdown(i,l,r); mid=(l+r)/2; if (aa<=mid) insert(i*2,l,mid,aa,b,m,c); if (b>mid) insert(i*2+1,mid+1,r,aa,b,m,c); tree[i]=(tree[i*2]+tree[i*2+1])%p; } long long work(int i,int l,int r,int aa,int b) { int mid; long long sum=0; if (aa<=l&&r<=b) { tree[i]=tree[i]%p; return tree[i]; } pushdown(i,l,r); mid=(l+r)/2; if (aa<=mid) sum=(sum+work(i*2,l,mid,aa,b))%p; if (b>mid) sum=(sum+work(i*2+1,mid+1,r,aa,b))%p; return sum; } int main() { freopen("seqb.in","r",stdin); freopen("seqb.out","w",stdout); int n,i,j,t,g,m,kind; long long ans,c=0; scanf("%d%lld",&n,&p); for (i=1;i<=n;++i) scanf("%lld",&a[i]); build(1,1,n); for (i=1;i<=399999;++i) delta[i][0]=1; scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&kind,&t,&g); if (kind==1) { scanf("%lld",&c); insert(1,1,n,t,g,c,0); } if (kind==2) { scanf("%lld",&c); insert(1,1,n,t,g,1,c); } if (kind==3) { ans=work(1,1,n,t,g)%p; printf("%lld\n",ans); } } fclose(stdin); fclose(stdout); }
cogs1008 贪婪大陆
题目大意:在一个给定区间内进行两种操作:给区间内每一个位置加上一个相同的与之前所放物品都不同的物品;查询区间内有几种物品。对于每一个查询都输出相应的结果。
思路:看到这个题目的时候有了一种错误的思路,以为是个简单的区间求和,可是仔细研究后发现并不是这样的。并不是将区间内的最大值或者和输出,因为有的时候可能两个相邻的区间放入物品,会默认为一个了,这样就出现了错误。于是就开始寻找思路(感谢dada)。我们可以将每个区间的左右端点插入到不同的树中,一个区间内物品的个数,其实就是1~r中左端点的个数减去1~(l-1)中右端点的个数,然后输出就可以了。这种类似于前缀和的思路要熟练掌握。
#include<iostream> #include<cstdio> using namespace std; int tree[400000][2]={0}; void insert1(int i,int l,int r,int a) { int mid; if (l==r&&l==a) { tree[i][0]++; return; } mid=(l+r)/2; if (a<=mid) insert1(i*2,l,mid,a); else insert1(i*2+1,mid+1,r,a); tree[i][0]=tree[i*2][0]+tree[i*2+1][0]; } void insert2(int i,int l,int r,int a) { int mid; if (l==r&&l==a) { tree[i][1]++; return; } mid=(l+r)/2; if (a<=mid) insert2(i*2,l,mid,a); else insert2(i*2+1,mid+1,r,a); tree[i][1]=tree[i*2][1]+tree[i*2+1][1]; } int work1(int i,int l,int r,int a,int b) { int mid,sum=0; if (a<=l&&r<=b) return tree[i][0]; mid=(l+r)/2; if (a<=mid) sum+=work1(i*2,l,mid,a,b); if (b>mid) sum+=work1(i*2+1,mid+1,r,a,b); return sum; } int work2(int i,int l,int r,int a,int b) { int mid,sum=0; if (a<=l&&r<=b) return tree[i][1]; mid=(l+r)/2; if (a<=mid) sum+=work2(i*2,l,mid,a,b); if (b>mid) sum+=work2(i*2+1,mid+1,r,a,b); return sum; } int main() { freopen("greedisland.in","r",stdin); freopen("greedisland.out","w",stdout); int n,m,i,j,q,l,r,ans1,ans2; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&q,&l,&r); if (q==1) { insert1(1,1,n,l); insert2(1,1,n,r); } else { ans1=work1(1,1,n,1,r); if (l==1) ans2=0; else ans2=work2(1,1,n,1,l-1); printf("%d\n",ans1-ans2); } } fclose(stdin); fclose(stdout); }
cogs265线段覆盖
题目大意:给区间放上或拿走黑布条,统计总区间内黑区间的个数和长度。
思路:线段树的区间覆盖,因为这个题没有查询操作,所以可以不用pushdown。然后就是updata和insert操作了。updata中要判断delta的值,然后进行更新(是清成全黑,或者根据孩子更新);insert中对于找到的区间要看是否为单元素的,若为单元素的则直接更新,否则要判断delta的范围来进行。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; struct use{ int lc,rc,sum,len; }tree[800000]; int delta[800000]={0}; void updata(int i,int l,int r) { int mid; if (delta[i]>0) { tree[i].lc=tree[i].rc=tree[i].sum=1;tree[i].len=r-l+1; } else { tree[i].lc=tree[i*2].lc;tree[i].rc=tree[i*2+1].rc; tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; if (tree[i*2].rc==tree[i*2+1].lc&&tree[i*2].rc==1) --tree[i].sum; tree[i].len=tree[i*2].len+tree[i*2+1].len; } } void insert(int i,int l,int r,int a,int b,int x) { int mid,tt; if (a<=l&&r<=b) { delta[i]+=x; if (l!=r) updata(i,l,r); else { if (delta[i]) tt=1; else tt=0; tree[i].lc=tree[i].rc=tt; tree[i].sum=tt;tree[i].len=tt; } return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,b,x); if (b>mid) insert(i*2+1,mid+1,r,a,b,x); updata(i,l,r); } int main() { freopen("xdfg.in","r",stdin); freopen("xdfg.out","w",stdout); int i,j,l,n,a,t,m; scanf("%d%d",&l,&n); for (i=1;i<=n;++i) { scanf("%d%d%d",&m,&a,&t); if (m==1) insert(1,0,l,a,a+t-1,1); else insert(1,0,l,a,a+t-1,-1); printf("%d %d\n",tree[1].sum,tree[1].len); } fclose(stdin); fclose(stdout); }
cogs775山海经
题目大意:在给定区间内找到最大的连续子段和。
思路:机房中的大神a了之后,在机房说思路,结果就弱弱的听到了,然后就。。。写了好久啊。。。这是一个线段树中保存很多状态的题,我们要更新这个区间的最大最小值,他们的位置,最大差和起止点。然后就是很简单但长达三十行的updata,然后就是弱弱的查询了。每个区间的最大差都是左右区间最大差和右区间最大值-左区间最小值中的较大值,然后保存下来,注意更新的顺序,能不用处理相等的情况就满足起止点最小的条件。
(一开始updata里面没有返回值,查了半个小时。。。要疯了!!!)
#include<iostream> #include<cstdio> using namespace std; struct use{ int maxi,mini,maxcha,st,en,pmax,pmin; }tree[400001]; int sum[100001]={0}; struct use updata(struct use ans1,struct use ans2) { struct use ans; if (ans1.maxi>=ans2.maxi) { ans.maxi=ans1.maxi;ans.pmax=ans1.pmax; } else { ans.maxi=ans2.maxi;ans.pmax=ans2.pmax; } if (ans1.mini<=ans2.mini) { ans.mini=ans1.mini;ans.pmin=ans1.pmin; } else { ans.mini=ans2.mini;ans.pmin=ans2.pmin; } ans.maxcha=-2100000000; if (ans1.maxcha>ans.maxcha) { ans.maxcha=ans1.maxcha;ans.st=ans1.st;ans.en=ans1.en; } if (ans2.maxi-ans1.mini>ans.maxcha) { ans.maxcha=ans2.maxi-ans1.mini;ans.st=ans1.pmin;ans.en=ans2.pmax; } if (ans2.maxcha>ans.maxcha) { ans.maxcha=ans2.maxcha;ans.st=ans2.st;ans.en=ans2.en; } return ans; } void build(int i,int l,int r) { int mid; if (l==r) { tree[i].maxi=tree[i].mini=sum[l]; tree[i].maxcha=-2100000000;tree[i].st=tree[i].en=tree[i].pmax=tree[i].pmin=l; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); tree[i]=updata(tree[i*2],tree[i*2+1]); } struct use work(int i,int l,int r,int a,int b) { struct use ans1,ans2,ans; int mid; ans1.maxi=ans2.maxi=-1000000000;ans1.maxcha=ans2.maxcha=-2100000000;ans1.mini=ans2.mini=1000000000; if (a<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (a<=mid) ans1=work(i*2,l,mid,a,b); if (b>mid) ans2=work(i*2+1,mid+1,r,a,b); ans=updata(ans1,ans2); return ans; } int main() { freopen("hill.in","r",stdin); freopen("hill.out","w",stdout); int n,m,a,b,i,j; struct use ans; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { scanf("%d",&j); sum[i]=sum[i-1]+j; } build(1,0,n); for (i=1;i<=m;++i) { scanf("%d%d",&a,&b); ans=work(1,0,n,a-1,b); printf("%d %d %d\n",ans.st+1,ans.en,ans.maxcha); } fclose(stdin); fclose(stdout); }
cogs1365软件安装
题目大意:找到最早的一个长度大于等于要求的给定区间,放一个软件。或者将一个区间释放。
思路:用结构体保存左右结点颜色和长度,区间最长长度。然后每次放一个软件的时候要将这个区间覆盖成1,释放的时候覆盖成0。一开始pushdown的时候没有判断delta是否为0(这里是覆盖,所以一定要判断,不然就。。。),最后把数组开大了一倍,然后才a。。。
#include<iostream> #include<cstdio> using namespace std; struct use{ int lc,rc,ls,rs,ms; }tree[400000]; int delta[400000]={0}; void updata(int i,int l,int r) { int mid; mid=(l+r)/2; tree[i].lc=tree[i*2].lc;tree[i].rc=tree[i*2+1].rc; tree[i].ls=tree[i*2].ls;tree[i].rs=tree[i*2+1].rs; tree[i].ms=max(tree[i*2].ms,tree[i*2+1].ms); if (tree[i*2].rc==tree[i*2+1].lc&&tree[i*2].rc==0) { tree[i].ms=max(tree[i].ms,tree[i*2].rs+tree[i*2+1].ls); if (tree[i].ls==mid-l+1) tree[i].ls=tree[i].ls+tree[i*2+1].ls; if (tree[i].rs==r-mid) tree[i].rs=tree[i].rs+tree[i*2].rs; } } void build(int i,int l,int r) { int mid; if (l==r) { tree[i].lc=tree[i].rc=0; tree[i].ls=tree[i].rs=tree[i].ms=1; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); updata(i,l,r); } void paint(int i,int l,int r,int x) { if (x==1) { tree[i].ls=tree[i].rs=tree[i].ms=r-l+1; tree[i].lc=tree[i].rc=0; } if (x==2) { tree[i].ls=tree[i].rs=tree[i].ms=0; tree[i].lc=tree[i].rc=1; } delta[i]=x; } void pushdown(int i,int l,int r) { int mid; mid=(l+r)/2; paint(i*2,l,mid,delta[i]); paint(i*2+1,mid+1,r,delta[i]); delta[i]=0; } int work(int i,int l,int r,int st) { int mid; mid=(l+r)/2; if (delta[i]!=0) pushdown(i,l,r); if (tree[i].ls>=st) return l; if (tree[i*2].ms>=st) return work(i*2,l,mid,st); if (tree[i*2].rs+tree[i*2+1].ls>=st) return (mid-tree[i*2].rs+1); if (tree[i*2+1].ms>=st) return work(i*2+1,mid+1,r,st); if (tree[i].rs>=st) return (r-tree[i].rs+1); } void insert(int i,int l,int r,int a,int b,int x) { int mid; if (delta[i]!=0) pushdown(i,l,r); if (a<=l&&r<=b) { paint(i,l,r,x); return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,b,x); if (b>mid) insert(i*2+1,mid+1,r,a,b,x); updata(i,l,r); } int main() { freopen("haoi13t4.in","r",stdin); freopen("haoi13t4.out","w",stdout); int n,m,i,j,kind,di,mi,po; scanf("%d%d",&n,&m); build(1,1,n); for (i=1;i<=m;++i) { scanf("%d",&kind); if (kind==1) { scanf("%d",&mi); if (tree[1].ms<mi) printf("0\n"); else { po=work(1,1,n,mi); printf("%d\n",po); insert(1,1,n,po,po+mi-1,2); } } else { scanf("%d%d",&di,&mi); insert(1,1,n,di,di+mi-1,1); } } fclose(stdin); fclose(stdout); }
cogs421HH的项链
题目大意:统计区间内元素的种类数。
思路:用了差分序列的思想,对于一种颜色,把这种颜色上一个位置之后的+1,当前位置的下一位-1,这样就能保证线段树中的这个区间是+1,最后查询1~每个查询的左端点的和(差分序列的和),就是答案了。
这个左端点困惑了我很久,现在终于明白了,因为只需要加到在这个区间里就可以了,(这里从左到右,保证了右端点一定在,只要在左端点右边就可以了),所以只需要加到左端点处。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct use{ int ll,rr,po; }ask[200001]; int ans[200001]={0},co[50001]={0},la[1000001]={0},tree[200001]={0}; int my_comp(const use &a,const use &b) { if (a.rr<b.rr) return 1; return 0; } void insert(int i,int l,int r,int a,int x) { int mid; if (l==r) { tree[i]+=x; return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,x); else insert(i*2+1,mid+1,r,a,x); tree[i]=tree[i*2]+tree[i*2+1]; } int work(int i,int l,int r,int a,int b) { int mid,sum=0; if (a<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (a<=mid) sum+=work(i*2,l,mid,a,b); if (b>mid) sum+=work(i*2+1,mid+1,r,a,b); return sum; } int main() { freopen("diff.in","r",stdin); freopen("diff.out","w",stdout); int n,m,i,j,l,r; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&co[i]); scanf("%d",&m); for (i=1;i<=m;++i) { scanf("%d%d",&ask[i].ll,&ask[i].rr); ask[i].po=i; } sort(ask+1,ask+m+1,my_comp); j=1; for (i=1;i<=n;++i) { insert(1,1,n+1,la[co[i]]+1,1); insert(1,1,n+1,i+1,-1); la[co[i]]=i; while (i==ask[j].rr) { ans[ask[j].po]=work(1,1,n+1,1,ask[j].ll); ++j; } if (j>m) break; } for (j=1;j<=m;++j) printf("%d\n",ans[j]); fclose(stdin); fclose(stdout); }
poj1151Atlantis
题目大意:矩形面积合并。
思路:扫描线。先离散(这里可以不用把具体的数保存在数组里,可以边做边求整数),将矩形的上下边加入到一个结构体里保存,左右端点、高度(纵坐标)、种类(是上边还是下边),然后就是将这些边排个升序,从下往上把边加入到线段树中(下边就+,上边就-),这里有一个小小的优化:因为我们不需要将标记下放,所以可以对一个区间的值直接求解,而不用pushdown。线段树中统计了这个区间中被边覆盖的长度(在更新长度的时候就用到了之前离散化的数组中的原值),这里的线段树中的结点并不是坐标点,而是两个坐标点之间的线段,所以要从1到n-1,每做一次就把ans+tree[1]*(bian[i+1].h-bian[i].h)。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; struct use{ double ll,rr,h; int kind,li,ri; }xian[10000]; double tree[400000]={0},cc[10000]={0}; int delta[400000]={0}; int my_comp(const use &a,const use &b) { if (a.h<b.h) return 1; return 0; } void updata(int i,int l,int r) { if (delta[i]>0) tree[i]=cc[r+1]-cc[l]; else { if (l==r) tree[i]=0; else tree[i]=tree[i*2]+tree[i*2+1]; } } void insert(int i,int l,int r,int a,int b,int x) { int mid; if (a<=l&&r<=b) { delta[i]+=x; updata(i,l,r); return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,b,x); if (b>mid) insert(i*2+1,mid+1,r,a,b,x); updata(i,l,r); } int main() { int n,i,j,tot=0,tot2=0,l,r,ci=0,size; double a,b,c,d,ans; while(scanf("%d",&n)==1) { if (n==0) break; ++ci;tot=0;tot2=0;ans=0; memset(xian,0,sizeof(xian)); memset(tree,0,sizeof(tree)); memset(delta,0,sizeof(delta)); for (i=1;i<=n;++i) { scanf("%lf%lf%lf%lf",&a,&b,&c,&d); ++tot;xian[tot].ll=a;xian[tot].rr=c;xian[tot].h=b;xian[tot].kind=1; ++tot;xian[tot].ll=a;xian[tot].rr=c;xian[tot].h=d;xian[tot].kind=-1; ++tot2;cc[tot2]=a; ++tot2;cc[tot2]=c; } sort(cc+1,cc+tot2+1); size=unique(cc+1,cc+tot2+1)-cc-1; sort(xian+1,xian+tot+1,my_comp); for (i=1;i<tot;++i) { xian[i].li=upper_bound(cc+1,cc+size+1,xian[i].ll)-cc-1; xian[i].ri=upper_bound(cc+1,cc+size+1,xian[i].rr)-cc-1; if (xian[i].li<xian[i].ri) insert(1,1,size-1,xian[i].li,xian[i].ri-1,xian[i].kind); ans=ans+tree[1]*(xian[i+1].h-xian[i].h); } printf("Test case #%d\nTotal explored area: %0.2f\n\n",ci,ans); } }
cogs263矩形周长
题目大意:矩形周长合并。
思路:扫描线。这道题的范围比较小,可以不离散(
主要是因为我一开始离散,然后错了),同面积,只是要保存端点的有无边和竖边的个数。然后稍微改一下updata。最后ans+=abs(tree[1].len-last);
ans+=tree[1].dd*(xian[i+1].h-xian[i].h);注意重边的合并。这里有负下标,所以向右移了10000(莫名的re。。。)#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; struct use{ int ll,rr,h,kind; }xian[100000]; struct uses{ int len,dd; bool lb,rb; }tree[100000]; int delta[100000]={0}; int my_comp(const use &a,const use &b) { if (a.h<b.h) return 1; return 0; } void updata(int i,int l,int r) { if (delta[i]>0) { tree[i].len=r-l+1; tree[i].dd=2; tree[i].lb=tree[i].rb=true; } else { if (l==r) { tree[i].len=tree[i].dd=0;tree[i].lb=tree[i].rb=false; } else { tree[i].len=tree[i*2].len+tree[i*2+1].len; tree[i].lb=tree[i*2].lb;tree[i].rb=tree[i*2+1].rb; tree[i].dd=tree[i*2].dd+tree[i*2+1].dd; if (tree[i*2].rb&&tree[i*2+1].lb) tree[i].dd-=2; } } } void insert(int i,int l,int r,int a,int b,int x) { int mid; if (a<=l&&r<=b) { delta[i]+=x; updata(i,l,r); return; } mid=(l+r)/2; if (a<=mid) insert(i*2,l,mid,a,b,x); if (b>mid) insert(i*2+1,mid+1,r,a,b,x); updata(i,l,r); } int main() { freopen("picture.in","r",stdin); freopen("picture.out","w",stdout); int n,i,j,l,r,a,b,c,d,tot=0,size,ans=0,last=0,ml,mr; scanf("%d",&n); ml=20000;mr=0; for (i=1;i<=n;++i) { scanf("%d%d%d%d",&a,&b,&c,&d); a+=10000;b+=10000;c+=10000;d+=10000; ++tot;xian[tot].ll=a;xian[tot].rr=c;xian[tot].h=b;xian[tot].kind=1; ++tot;xian[tot].ll=a;xian[tot].rr=c;xian[tot].h=d;xian[tot].kind=-1; ml=min(ml,a); mr=max(mr,c); } sort(xian+1,xian+tot+1,my_comp); for (i=1;i<=tot;++i) { if (xian[i].ll<xian[i].rr) insert(1,ml,mr-1,xian[i].ll,xian[i].rr-1,xian[i].kind); ans+=abs(tree[1].len-last); ans+=tree[1].dd*(xian[i+1].h-xian[i].h); last=tree[1].len; } printf("%d\n",ans); fclose(stdin); fclose(stdout); }
当然这个题也可以不用线段树做,dada大神用了神奇的差分序列,附上code。神神神神!!!
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> int delta[10002]; int main(){ int left[5000],right[5000],down[5000],up[5000],X[10000],Y[10000],N,i,j; freopen("picture.in","r",stdin); freopen("picture.out","w",stdout); scanf("%d",&N); for(i=0;i<N;++i){ scanf("%d%d%d%d",left+i,down+i,right+i,up+i); X[i]=left[i]; X[i+N]=right[i]; Y[i]=down[i]; Y[i+N]=up[i]; } sort(X,X+(N<<1)),sort(Y,Y+(N<<1)); int xtot=unique(X,X+(N<<1))-X,ytot=unique(Y,Y+(N<<1))-Y; for(i=0;i<N;++i){ left[i]=lower_bound(X,X+xtot,left[i])-X; right[i]=lower_bound(X,X+xtot,right[i])-X; down[i]=lower_bound(Y,Y+ytot,down[i])-Y; up[i]=lower_bound(Y,Y+ytot,up[i])-Y; } int ans=0,now; for(i=1;i<xtot;++i){ memset(delta,0,sizeof(delta)); for(j=0;j<N;++j) if(left[j]<i&&right[j]>=i){ delta[down[j]]+=1; delta[up[j]]+=-1; } now=0; for(j=0;j<ytot;++j){ now+=delta[j]; if(!now&&delta[j]) ans+=(X[i]-X[i-1])<<1; } } for(i=1;i<ytot;++i){ memset(delta,0,sizeof(delta)); for(j=0;j<N;++j) if(down[j]<i&&up[j]>=i){ delta[left[j]]+=1; delta[right[j]]+=-1; } now=0; for(j=0;j<xtot;++j){ now+=delta[j]; if(!now&&delta[j]) ans+=(Y[i]-Y[i-1])<<1; } } printf("%d",ans); }
cogs859数列
题目大意:给定一个长度为n的数列,求这样的三个元素 ai,aj,ak 的个数,满足 ai<aj>ak,且 i<j<k。
思路:这道题比较简单,一看到的时候,用暴力的线段树做,得了80分,有点可惜。后来改进了思路,利用了题目中一个很好地条件(ai<=32767),这样的话就可以将线段树的结点表示这些整数,区间求和,维护两次线段树就可以了,最后将一个数两边比它小的个数相乘,就得到了正确结果。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long tree[200000]={0},minl[50001]={0},minr[50001]={0},a[50001]={0}; void insert(int i,int l,int r,int aa) { int mid; if (l==r) { ++tree[i]; return; } mid=(l+r)/2; if (aa<=mid) insert(i*2,l,mid,aa); else insert(i*2+1,mid+1,r,aa); tree[i]=tree[i*2]+tree[i*2+1]; } long long work(int i,int l,int r,int aa,int b) { long long ans=0; int mid; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) ans+=work(i*2,l,mid,aa,b); if (b>mid) ans+=work(i*2+1,mid+1,r,aa,b); return ans; } int main() { freopen("queueb.in","r",stdin); freopen("queueb.out","w",stdout); int n,i,j; long long ans=0; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%lld",&a[i]); a[i]+=2; } for (i=1;i<=n;++i) { insert(1,1,32769,a[i]); if (i>1) minl[i]=work(1,1,32769,1,a[i]-1); } memset(tree,0,sizeof(tree)); for (i=n;i>=1;--i) { insert(1,1,32769,a[i]); if (i<n) minr[i]=work(1,1,32769,1,a[i]-1); if (i>1) ans+=minl[i]*minr[i]; } printf("%lld\n",ans); fclose(stdin); fclose(stdout); }
cogs1260三元数对
题目大意:给定一个长度为n的数列,求这样的三个元素 ai,aj,ak 的个数,满足 ai<aj<ak,且 i<j<k。
思路:和上一题相同,只是要求右半段比它大的值。因为这一题的ai是longint,而n只有30000,所以就需要离散化一下,就能保证ai也在30000以内了(适当的+1,所以就会稍微大一点)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; long long tree[200000]={0},minl[30001]={0},maxr[30001]={0}; int a[30001]={0},c[30001]={0}; void insert(int i,int l,int r,int aa) { int mid; if (l==r) { ++tree[i]; return; } mid=(l+r)/2; if (aa<=mid) insert(i*2,l,mid,aa); else insert(i*2+1,mid+1,r,aa); tree[i]=tree[i*2]+tree[i*2+1]; } long long work(int i,int l,int r,int aa,int b) { long long ans=0; int mid; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) ans+=work(i*2,l,mid,aa,b); if (b>mid) ans+=work(i*2+1,mid+1,r,aa,b); return ans; } int main() { freopen("three.in","r",stdin); freopen("three.out","w",stdout); int n,i,j,size,maxn=0; long long ans=0; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%d",&a[i]); c[i]=a[i]; } sort(c+1,c+n+1); size=unique(c+1,c+n+1)-c-1; for (i=1;i<=n;++i) { a[i]=upper_bound(c+1,c+size+1,a[i])-c; maxn=max(a[i],maxn); } ++maxn; for (i=1;i<=n;++i) { insert(1,1,maxn,a[i]); minl[i]=work(1,1,maxn,1,a[i]-1); } memset(tree,0,sizeof(tree)); for (i=n;i>=1;--i) { insert(1,1,maxn,a[i]); maxr[i]=work(1,1,maxn,a[i]+1,maxn); ans+=minl[i]*maxr[i]; } printf("%lld",ans); fclose(stdin); fclose(stdout); }
cogs1212奶牛排队
题目大意:找一段区间满足左右端点分别为最小最大值,且中间没有一头牛的高度和端点处的一样。
思路:用线段树维护最大最小值,使最大值尽量靠左,且最小值尽量靠右。然后用深搜找一个区间,若最大值的下标==最小值的下标,就return;若最大值下标<最小值下标,则搜(l,lmax)(lmax+1,lmin-1)(lmin,r);若最大值下标>最小值小标,更新ans,然后继续搜(l,lmin-1)(lmax+1,r)。
(一开始wa了很久,就是因为没有初始化。。。)
#include<iostream> #include<cstdio> using namespace std; struct use{ int maxn,minn,pmax,pmin; }tree[400000]; int a[100001]={0},ans=0,n; void build(int i,int l,int r) { int mid; if (l==r) { tree[i].maxn=tree[i].minn=a[l]; tree[i].pmax=tree[i].pmin=l; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); if (tree[i*2].maxn>=tree[i*2+1].maxn) { tree[i].maxn=tree[i*2].maxn;tree[i].pmax=tree[i*2].pmax; } else { tree[i].maxn=tree[i*2+1].maxn;tree[i].pmax=tree[i*2+1].pmax; } if (tree[i*2].minn<tree[i*2+1].minn) { tree[i].minn=tree[i*2].minn;tree[i].pmin=tree[i*2].pmin; } else { tree[i].minn=tree[i*2+1].minn;tree[i].pmin=tree[i*2+1].pmin; } } struct use work(int i,int l,int r,int aa,int b) { int mid; struct use ans1,ans2,ans3; ans1.maxn=ans2.maxn=-2100000000;ans1.pmax=ans2.pmax=2100000000; ans1.minn=ans2.minn=2100000000;ans1.pmin=ans2.pmin=0; if (aa<=l&&r<=b) return tree[i]; mid=(l+r)/2; if (aa<=mid) ans1=work(i*2,l,mid,aa,b); if (b>mid) ans2=work(i*2+1,mid+1,r,aa,b); if (ans1.maxn>=ans2.maxn) { ans3.maxn=ans1.maxn;ans3.pmax=ans1.pmax; } else { ans3.maxn=ans2.maxn;ans3.pmax=ans2.pmax; } if (ans1.minn<ans2.minn) { ans3.minn=ans1.minn;ans3.pmin=ans1.pmin; } else { ans3.minn=ans2.minn;ans3.pmin=ans2.pmin; } return ans3; } void dfs(int l,int r) { struct use aa,a1,a2; int d; if (r<=l) return; d=r-l+1; if (d<=ans) return; aa=work(1,1,n,l,r); if (aa.pmax==aa.pmin) return; if (aa.maxn==aa.minn) return; if (aa.pmax>aa.pmin) { ans=max(ans,aa.pmax-aa.pmin+1); dfs(l,aa.pmin-1); dfs(aa.pmax+1,r); } if (aa.pmax<aa.pmin) { dfs(l,aa.pmax); dfs(aa.pmax+1,aa.pmin-1); dfs(aa.pmin,r); } } int main() { freopen("tahort.in","r",stdin); freopen("tahort.out","w",stdout); int i,j; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); build(1,1,n); dfs(1,n); if (ans==1) ans=2; printf("%d\n",ans); fclose(stdin); fclose(stdout); }
cogs1543最优挤奶法
题目大意:给一定机器和他们的产奶量,求d天最多产奶量的和,每天都可以更改一个机器的产奶量。要求是相邻的两台机器不能同时开。
思路:本来想用tree一个数组保存这个区间的最大和,可是后来发现,这样的局部最优不能带来整体最优,所以一个新的算法就诞生了。保存这个区间取不取左右端点时的最大和,然后每次都对一个区间的四种状态进行更新(写了很牛的三个取最大值),然后没有查询的求和就可以了。
#include<iostream> #include<cstdio> using namespace std; struct use{ long long c1,c2,c3,c4; }tree[160000]={0}; int a[40001]={0}; void updata(int i) { tree[i].c1=max(tree[i*2].c1+tree[i*2+1].c1,max(tree[i*2].c1+tree[i*2+1].c4,tree[i*2].c3+tree[i*2+1].c1)); tree[i].c2=max(tree[i*2].c2+tree[i*2+1].c3,max(tree[i*2].c4+tree[i*2+1].c2,tree[i*2].c4+tree[i*2+1].c3)); tree[i].c3=max(tree[i*2].c1+tree[i*2+1].c2,max(tree[i*2].c1+tree[i*2+1].c3,tree[i*2].c3+tree[i*2+1].c3)); tree[i].c4=max(tree[i*2].c4+tree[i*2+1].c4,max(tree[i*2].c2+tree[i*2+1].c1,tree[i*2].c4+tree[i*2+1].c1)); } void build(int i,int l,int r) { int mid; if (l==r) { tree[i].c2=a[l]; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); updata(i); } void insert(int i,int l,int r,int x,int y) { int mid; if (l==r) { tree[i].c2=y; tree[i].c1=tree[i].c3=tree[i].c4=0; return; } mid=(l+r)/2; if (x<=mid) insert(i*2,l,mid,x,y); else insert(i*2+1,mid+1,r,x,y); updata(i); } int main() { freopen("optmilk.in","r",stdin); freopen("optmilk.out","w",stdout); int i,j,n,k,d,m; long long ans=0,sum; scanf("%d%d",&n,&d); for (i=1;i<=n;++i) scanf("%d",&a[i]); build(1,1,n); for (i=1;i<=d;++i) { scanf("%d%d",&k,&m); insert(1,1,n,k,m); sum=max(tree[1].c1,max(tree[1].c2,max(tree[1].c3,tree[1].c4))); ans+=sum; } printf("%lld\n",ans); fclose(stdin); fclose(stdout); }
cogs1829普通平衡树
题目大意:支持插入数x、删除数x、查找数x排名、查找排名为x的数、找数x的前驱、后继。
思路:看到题目和数据范围,很自然的想到了离散化,弄一个映射一样的东西,方便由离散化后的数找回原数。然后就是对离散化之后的整数建树了,每个区间保存有多少个数、最右面的和最左面的数在线段树中的下标(即离散化之后的数)。然后就是写插入和删除了,这里是单点修改(+1||-1)。查找数x的排名可以用1~x-1的区间求和+1来做(一开始这里读错了题,wa了一半,对于一个数x,若插入多次就当多个数看待,但是找到排名最小的输出)。查找排名为x的数可以借用以前约瑟夫问题的思路,先看左子树、再看右子树,决定去哪边找,若去右子树找还要减去左子树的个数。找数x的前驱和后继可以用一个函数,用结构体返回,利用tree里面的值,直接找。
这道题目综合了常见的线段树的各种操作,本身题目不难懂(虽然我一开始理解错了题意),但多种操作的组合很容易出现错误,这就要求对线段树又清晰地认识和体会。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct use{ int sum,maxn,minn; }tree[400000]={0}; struct use1{ int kind,num; }a[100001]; int cc[100001]={0},dd[100001]={0}; void updata(int i) { tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; if (tree[i*2+1].maxn==0) tree[i].maxn=tree[i*2].maxn; else tree[i].maxn=tree[i*2+1].maxn; if (tree[i*2].minn==0) tree[i].minn=tree[i*2+1].minn; else tree[i].minn=tree[i*2].minn; } void insert(int i,int l,int r,int x,int y) { int mid; if (l==r) { tree[i].sum+=y; tree[i].maxn=tree[i].minn=l; if (tree[i].sum==0) { tree[i].maxn=tree[i].minn=0; } return; } mid=(l+r)/2; if (x<=mid) insert(i*2,l,mid,x,y); else insert(i*2+1,mid+1,r,x,y); updata(i); } int work1(int i,int l,int r,int x,int y) { int mid,ans=0; if (x<=l&&r<=y) return tree[i].sum; mid=(l+r)/2; if (x<=mid) ans+=work1(i*2,l,mid,x,y); if (y>mid) ans+=work1(i*2+1,mid+1,r,x,y); return ans; } int work2(int i,int l,int r,int x) { int mid; if (l==r) return l; mid=(l+r)/2; if (x<=tree[i*2].sum) return work2(i*2,l,mid,x); else return work2(i*2+1,mid+1,r,x-tree[i*2].sum); } struct use work(int i,int l,int r,int x,int y) { int mid; struct use ans1,ans2,ans; ans1.maxn=ans1.minn=ans2.maxn=ans2.minn=0; if (x<=l&&r<=y) return tree[i]; mid=(l+r)/2; if (x<=mid) ans1=work(i*2,l,mid,x,y); if (y>mid) ans2=work(i*2+1,mid+1,r,x,y); if (ans2.maxn==0) ans.maxn=ans1.maxn; else ans.maxn=ans2.maxn; if (ans1.minn==0) ans.minn=ans2.minn; else ans.minn=ans1.minn; return ans; } int main() { freopen("phs.in","r",stdin); freopen("phs.out","w",stdout); int n,opt,x,i,j,totc=0,size,ans1; struct use ans; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%d%d",&a[i].kind,&a[i].num); if (a[i].kind!=4) { ++totc; cc[totc]=a[i].num; } } sort(cc+1,cc+totc+1); size=unique(cc+1,cc+totc+1)-cc-1; for (i=1;i<=n;++i) { if (a[i].kind!=4) { opt=a[i].num; a[i].num=upper_bound(cc+1,cc+size+1,a[i].num)-cc-1; dd[a[i].num]=opt; } } for (i=1;i<=n;++i) { if (a[i].kind==1) insert(1,1,size,a[i].num,1); if (a[i].kind==2) insert(1,1,size,a[i].num,-1); if (a[i].kind==3) { if (a[i].num==1) ans1=1; else ans1=work1(1,1,size,1,a[i].num-1)+1; printf("%d\n",ans1); } if (a[i].kind==4) { ans1=work2(1,1,size,a[i].num); printf("%d\n",dd[ans1]); } if (a[i].kind==5) { ans=work(1,1,size,1,a[i].num-1); printf("%d\n",dd[ans.maxn]); } if (a[i].kind==6) { ans=work(1,1,size,a[i].num+1,size); printf("%d\n",dd[ans.minn]); } } fclose(stdin); fclose(stdout); }
cogs3032摆放球
题目大意:有n个橱窗,操作m次,每次放或拿一个球,放的时候要求使球离最近的球尽量远,拿的时候告诉球的编号。
思路:一开始竟然没有思路,后来想了想,这种操作也只有线段树能完成了。保存这个区间中最长的区间以及左右端点,和左右端点延伸的最长区间,然后每次插入的时候比较左右端点的长度和中间最长的长度/2(如果长度为偶数就-1),找到大的并且靠前的插入,就可以了。但是,这里有一个问题,就是对于一个区间长度为1、2的他们视为相同的区间,即如果长度为1在前面的话就放到长度为1的里面,然后就能a了这道题了。我们可以用映射的思想把编号和线段树中的位置对应下来。
这道题唯一仁慈的就是单点修改。
#include<iostream> #include<cstdio> using namespace std; struct use{ int ls,rs,lo,ll,rr,lls,rrs; }tree[1000000]; int pos[1000001]={0}; void updata(int i,int l,int r) { int mid; mid=(l+r)/2; tree[i].ls=tree[i*2].ls;tree[i].rs=tree[i*2+1].rs; tree[i].lo=tree[i*2].lo;tree[i].ll=tree[i*2].ll;tree[i].rr=tree[i*2].rr; tree[i].lls=tree[i*2].lls;tree[i].rrs=tree[i*2+1].rrs; if ((tree[i*2+1].ls+tree[i*2].rs+1)/2>(tree[i].lo+1)/2) { tree[i].lo=tree[i*2+1].ls+tree[i*2].rs; if (tree[i*2].rs>0) tree[i].ll=tree[i*2].rrs; else tree[i].ll=mid+1; if (tree[i*2+1].ls>0) tree[i].rr=tree[i*2+1].lls; else tree[i].rr=mid; } if ((tree[i*2+1].lo+1)/2>(tree[i].lo+1)/2) { tree[i].lo=tree[i*2+1].lo; tree[i].ll=tree[i*2+1].ll;tree[i].rr=tree[i*2+1].rr; } if (tree[i*2].ls==mid-l+1) { if (tree[i*2+1].ls>0) { tree[i].ls=tree[i].ls+tree[i*2+1].ls; tree[i].lls=tree[i*2+1].lls; } else tree[i].lls=mid; } if (tree[i*2+1].rs==r-mid) { if (tree[i*2].rs>0) { tree[i].rs=tree[i].rs+tree[i*2].rs; tree[i].rrs=tree[i*2].rrs; } else tree[i].rrs=mid+1; } } void build(int i,int l,int r) { int mid; if (l==r) { tree[i].ls=tree[i].rs=tree[i].lo=1; tree[i].ll=tree[i].rr=tree[i].lls=tree[i].rrs=l; return; } mid=(l+r)/2; build(i*2,l,mid); build(i*2+1,mid+1,r); updata(i,l,r); } void insert(int i,int l,int r,int x) { int mid; if (l==r) { tree[i].ls=tree[i].rs=tree[i].lo=0; tree[i].lls=tree[i].ll=tree[i].rrs=tree[i].rr=l; return; } mid=(l+r)/2; if (x<=mid) insert(i*2,l,mid,x); else insert(i*2+1,mid+1,r,x); updata(i,l,r); } void del(int i,int l,int r,int x) { int mid; if (l==r) { tree[i].ls=tree[i].rs=tree[i].lo=1; tree[i].lls=tree[i].rrs=tree[i].ll=tree[i].rr=l; return; } mid=(l+r)/2; if (x<=mid) del(i*2,l,mid,x); else del(i*2+1,mid+1,r,x); updata(i,l,r); } int main() { int n,m,t,i,j,kind,a,b,c; scanf("%d%d",&n,&m); build(1,1,n); for (i=1;i<=m;++i) { scanf("%d%d",&kind,&t); if (kind==1) { a=tree[1].ls-1;b=tree[1].lo/2;c=tree[1].rs-1; if (tree[1].lo%2==0) --b; if (a>=b&&a>=c) { insert(1,1,n,1); pos[t]=1; } if (b>a&&b>=c) { pos[t]=tree[1].ll+b; insert(1,1,n,tree[1].ll+b); } if (c>a&&c>b) { insert(1,1,n,n); pos[t]=n; } printf("%d\n",pos[t]); } else { del(1,1,n,pos[t]); pos[t]=0; } } }
某个大神的模拟题T3 timer
题目大意:求n个元素中的k元逆序对(k个单减的元素),n个元素不重复。
思路:每个位置开一个k大小的数组,将数字排序后从小到大插入线段树,每次插入的值(一个数组)由这个位置后面的区间和(数组对应下标的和)平移1位(因为对于这个元素,之前逆序对的长度加了1)。最后答案就是整个线段树k位置的和。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define p 1000000007 using namespace std; struct use{ long long dp[15]; void init(){memset(dp,0,sizeof(dp));} }tree[maxnode*4]={0}; struct uu{ int num,po; }ai[maxnode]={0}; int k; int my_comp(const uu x,const uu y){return (x.num<y.num);} void updata(int i) { int j; for (j=0;j<=k;++j) tree[i].dp[j]=(tree[i*2].dp[j]+tree[i*2+1].dp[j])%p; } use task(int i,int l,int r,int ll,int rr) { int mid,j; use ans1,ans2; if (ll<=l&&r<=rr) return tree[i]; mid=(l+r)/2;ans1.init();ans2.init(); if (ll<=mid) ans1=task(i*2,l,mid,ll,rr); if (rr>mid) ans2=task(i*2+1,mid+1,r,ll,rr); for (j=1;j<=k;++j) ans1.dp[j]=(ans1.dp[j]+ans2.dp[j])%p; return ans1; } void tch(int i,int l,int r,int x,use y) { int mid; if (l==r){tree[i]=y;return;} mid=(l+r)/2; if (x<=mid) tch(i*2,l,mid,x,y); else tch(i*2+1,mid+1,r,x,y); updata(i); } int main() { freopen("timer.in","r",stdin); freopen("timer.out","w",stdout); int n,m,i,j; use xx; scanf("%d%d",&n,&k); if (k==1) printf("0\n"); else { for (i=1;i<=n;++i) { scanf("%d",&ai[i].num);ai[i].po=i; } sort(ai+1,ai+n+1,my_comp); for (i=1;i<=n;++i) { if (ai[i].po<n) { xx=task(1,1,n,ai[i].po+1,n); for (j=k;j>=2;--j) xx.dp[j]=xx.dp[j-1]; } else xx.init(); xx.dp[1]=1; tch(1,1,n,ai[i].po,xx); } printf("%I64d\n",tree[1].dp[k]); } fclose(stdin); fclose(stdout); }
bzoj1018 SHOI堵塞的交通
题目大意:2×c的网格,给出一些修改:使相邻格子联通或者堵塞,还有询问两点间的联通状况。
思路:线段树维护一个区间四个头互相连通的关系,这里的关系只是借助区间内部边联通的情况。在询问中,还要讨论通过外部联通的情况,取c小的一个为前,分类讨论:1)两个都在第一列;2)两个都在第二列;3)两个一上一下;4)两个一下一上。每一种情况里有四种联通的情况分类(这里很容易漏掉,一定要十分注意)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 using namespace std; struct use{ bool updown,self[3],luru,ldrd,lurd,ldru,luld,rurd; void init(){luru=ldrd=lurd=ldru=luld=rurd=false;} }tree[maxnode*4]={false}; char ss[10]; int c; void change(int i) { if (tree[i].updown) tree[i].lurd=tree[i].ldru=tree[i].luld=tree[i].rurd=true; else tree[i].lurd=tree[i].ldru=tree[i].luld=tree[i].rurd=false; } use updata(use x1,use x2) { use ans;ans.init(); ans.self[1]=x2.self[1];ans.self[2]=x2.self[2]; if ((x1.luru&&x1.self[1]&&x2.luru)||(x1.lurd&&x1.self[2]&&x2.ldru)) ans.luru=true; if ((x1.ldrd&&x1.self[2]&&x2.ldrd)||(x1.ldru&&x1.self[1]&&x2.lurd)) ans.ldrd=true; if ((x1.luru&&x1.self[1]&&x2.lurd)||(x1.lurd&&x1.self[2]&&x2.ldrd)) ans.lurd=true; if ((x1.ldrd&&x1.self[2]&&x2.ldru)||(x1.ldru&&x1.self[1]&&x2.luru)) ans.ldru=true; if ((x1.luld)||(x1.luru&&x1.self[1]&&x2.luld&&x1.self[2]&&x1.ldrd)) ans.luld=true; if ((x2.rurd)||(x2.luru&&x1.self[1]&&x1.rurd&&x1.self[2]&&x2.ldrd)) ans.rurd=true; return ans; } void build(int i,int l,int r) { int mid; if (l==r){tree[i].luru=tree[i].ldrd=true;return;} mid=(l+r)/2;build(i*2,l,mid);build(i*2+1,mid+1,r); tree[i]=updata(tree[i*2],tree[i*2+1]); } void insh(int i,int l,int r,int x,int y,int kk) { int mid; if (l==r){tree[i].self[x]=kk;return;} mid=(l+r)/2; if (y<=mid) insh(i*2,l,mid,x,y,kk); else insh(i*2+1,mid+1,r,x,y,kk); tree[i]=updata(tree[i*2],tree[i*2+1]); } void insl(int i,int l,int r,int y,int kk) { int mid; if (l==r){tree[i].updown=kk;change(i);return;} mid=(l+r)/2; if (y<=mid) insl(i*2,l,mid,y,kk); else insl(i*2+1,mid+1,r,y,kk); tree[i]=updata(tree[i*2],tree[i*2+1]); } use ask(int i,int l,int r,int c1,int c2) { use ans1,ans2;int mid; bool f1,f2;f1=f2=false; if (c1<=l&&r<=c2) return tree[i]; mid=(l+r)/2; if (c1<=mid){ans1=ask(i*2,l,mid,c1,c2);f1=true;} if (c2>mid){ans2=ask(i*2+1,mid+1,r,c1,c2);f2=true;} if (f1&&f2) return updata(ans1,ans2); else { if (f1) return ans1; else return ans2; } } bool judge(int r1,int c1,int r2,int c2) { use x1,x2,x3; if (c1==c2&&r1==r2) return true; x1=ask(1,1,c,c1,c2);x2=ask(1,1,c,1,c1);x3=ask(1,1,c,c2,c); if (r1==r2) { if (r1==1) { if (x1.luru||(x1.ldrd&&x2.rurd&&x3.luld) ||(x2.rurd&&x1.ldru)||(x3.luld&&x1.lurd)) return true; } else { if (x1.ldrd||(x1.luru&&x2.rurd&&x3.luld) ||(x2.rurd&&x1.lurd)||(x3.luld&&x1.ldru)) return true; } } else { if (r1==1) { if (x1.lurd||(x2.rurd&&x1.ldrd)|| (x1.luru&&x3.luld)||(x2.rurd&&x3.luld&&x1.ldru)) return true; } else { if (x1.ldru||(x1.luru&&x2.rurd)|| (x1.ldrd&&x3.luld)||(x2.rurd&&x3.luld&&x1.lurd)) return true; } } return false; } int main() { int i,j,r1,c1,r2,c2; scanf("%d",&c);build(1,1,c); while(scanf("%s",&ss)==1) { if (ss[0]!='E'&&ss[0]!='C'&&ss[0]!='O'&&ss[0]!='A') continue; if (ss[0]=='E') break; scanf("%d%d%d%d",&r1,&c1,&r2,&c2); if (ss[0]=='C') { if (r1==r2) insh(1,1,c,r1,min(c1,c2),0); else insl(1,1,c,c1,0); } if (ss[0]=='O') { if (r1==r2) insh(1,1,c,r1,min(c1,c2),1); else insl(1,1,c,c1,1); } if (ss[0]=='A') { if (c1>c2) {swap(r1,r2);swap(c1,c2);} if(judge(r1,c1,r2,c2)) printf("Y\n"); else printf("N\n"); } } }