@Asuna
2017-05-15T12:27:37.000000Z
字数 7937
阅读 1360
题解
题意: 有一个旅馆,有N个连续的房间,开始的时候没有人住,下面有M次‘1’或‘2’操作。操作“1 D”表示有D个人进来选房间,他们必须是住在连号的房间,如果不满足这个条件,输出“0”,否则,输出他们这几个人中房间号码最小的一个;操作“2 X D”表示Hotel从房间号X开始D个房间被清空。
题解:线段树的区间更新(操作2)+区间合并(操作1),在线段树上面维护三个值,分别是对于[l,r]这段区间,从l开始的连续区间的长度,以r结尾的连续区间的长度,[l,r]之间最长连续区间的长度。然后是两个操作的方法:
1、合并两个区间[a,b],[c,d] 的时候,得到的新的区间[a,d]的最大连续区间长度为:Length(a,d)=max{Length(a,b),Length(c,d),End(b)+Begin(c)};
其中End(b)表示的是以b为结尾的连续区间长度,Begin(c)表示的是以c开头的连续区间的长度。
2、更新整个给定区间的时候,由于直接单点逐一更新会导致复杂度爆表,因此需要用到一个lazy-tag(js教本鶸的)。lazy-tag的定义如下:
(1)访问某一节点时,清空该节点标记并把此标记下传给它的左右孩子。
(2)当我们所要修改(or查询)的区间[a,b]与该节点所代表的区间相合时,给这一节点作上标记,退出这层递归。
(3)如果不相合,则继续修改(or查询)左右子节点。具体方法是:当[a,b]在此节点所代表区间的左边时访问左子节点,在右边时访问右子节点,否则按如下方式访问:(lchild,a,p^.m)和(rchild,p^.m+1,b)
(4)清空左右子节点,并把左右子节点的值上传至根节点。
$$说白了就相当于给非叶子结点的更新操作都是一个标记,表示他要修改,但在修改叶子节点前并不更新他的值,直到做到叶子节点的时候更新叶子节点的值一并更新。
参考代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;struct node{int ln, rn, mn;}f[400010];int tag[400010],handle,ans,X,D,n,m;void PushUp(int rt,int l,int r){f[rt].ln=f[rt<<1].ln;f[rt].rn=f[rt<<1|1].rn;f[rt].mn=max(f[rt<<1].rn+f[rt<<1|1].ln,max(f[rt<<1].mn,f[rt<<1|1].mn));int mid=(l+r)/2;if(f[rt<<1].mn==mid-l+1)f[rt].ln+=f[rt<<1|1].ln;if(f[rt<<1|1].mn==r-(mid+1)+1)f[rt].rn+=f[rt<<1].rn;}void PushDown(int rt,int l,int r){if(tag[rt]!=-1){tag[rt<<1]=tag[rt<<1|1]=tag[rt];int mid=(l+r)/2;f[rt<<1].ln=f[rt<<1].rn=f[rt<<1].mn=tag[rt]*(mid-l+1);f[rt<<1|1].ln=f[rt<<1|1].rn=f[rt<<1|1].mn=tag[rt]*(r-(mid+1)+1);tag[rt]=-1;}}void Build(int l, int r, int rt){f[rt].ln=f[rt].rn=f[rt].mn=r-l+1;tag[rt]=1;if(l==r) return ;int mid=(l+r)/2;Build(l,mid,rt<<1);Build(mid+1,r,rt<<1|1);}void Update(int L,int R,bool isClear,int l,int r,int rt){if(L<=l && r<=R){if(isClear){f[rt].ln=f[rt].rn=f[rt].mn=r-l+1;tag[rt]=1;}else{f[rt].ln=f[rt].rn=f[rt].mn=0;tag[rt]=0;}return;}PushDown(rt,l,r);int mid=(l+r)/2;if (L<=mid)Update(L,R,isClear,l,mid,rt<<1 );if (mid<R)Update(L,R,isClear,mid+1,r,rt<<1|1);PushUp(rt,l,r);}int Query(int len,int l,int r,int rt){if (f[rt].ln>=len) return l;int mid=(l+r)/2;if(f[rt << 1].mn>=len)return Query(len,l,mid,rt<<1);elseif (f[rt<<1].rn+f[rt<<1|1].ln>=len)return mid-f[rt<<1].rn+1;elsereturn Query(len,mid+1,r,rt<<1|1);}int main(){while (cin>>n>>m){Build(1,n,1);while (m--){scanf("%d",&handle);if (handle&1){scanf("%d",&D);if (f[1].mn<D){printf("0\n");continue;}ans=Query(D,1,n,1);printf("%d\n",ans);Update(ans,ans+D-1,false,1,n,1);}else{scanf("%d%d",&X,&D);Update(X,X+D-1,true,1,n,1);}}}return 0;}
题意:一长由N小段组成的长条,每小段的初始颜色为2。现执行M个操作,每个操作是以下两种中的一种:
P a b c:将段a到段b涂成颜色c,c是1, 2, ... 30中的一种。
Q a b:问段a到段b之间有哪几种颜色,按颜色大小从小到大输出。
题解:线段树的区间查询和区间修改。
对于P操作,就是区间修改,也需要和Problem A一样用到lazy-tag。
对于Q操作,每查询到一个有颜色的区间就丢进set中,最后把set中的元素一起输出。
参考代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<set>using namespace std;int n,m,f[4000010];set<int> st;set<int>::iterator it;void pushDown(int rt){if(f[rt]!=-1){f[rt<<1]=f[rt<<1|1]=f[rt];f[rt]=-1;}}void pushUp(int rt){if (f[rt<<1]==f[rt<<1|1]){f[rt]=f[rt<<1];}else f[rt]=-1;}void build(int rt, int L, int R){f[rt]=-1;if (L==R){f[rt]=2;return ;}int mid=(L+R)/2;build(rt<<1,L,mid);build(rt<<1|1,mid+1,R);pushUp(rt);}void update(int rt, int L, int R, int ql, int qr, int val){if(ql<=L && R<=qr){f[rt]=val;return ;}int mid=(L+R)/2;pushDown(rt);if (ql<=mid) update(rt<<1,L,mid,ql,qr,val);if (qr>mid) update(rt<<1|1,mid+1,R,ql,qr,val);pushUp(rt);}void query(int rt, int L, int R, int ql, int qr){if (f[rt]!=-1 && ql<=L && R<=qr){st.insert(f[rt]);return ;}int mid=(L+R)/2;pushDown(rt);if (ql<=mid) query(rt<<1,L,mid,ql,qr);if (qr>mid) query(rt<<1|1,mid+1,R,ql,qr);pushUp(rt);}int main(){char op[5];int ql,qr,val;while(cin>>n>>m){if (n==0 && m==0) break;build(1,1,n);while(m--){scanf("%s",op);if(op[0]=='P'){scanf("%d%d%d",&ql,&qr,&val);update(1,1,n,ql,qr,val);}else{scanf("%d%d",&ql,&qr);st.clear();query(1,1,n,ql,qr);it=st.begin();printf("%d",*it); it++;for(; it!=st.end(); it++){printf(" %d",*it);}printf("\n");}}}return 0;}
题意:输入n,k,问n个数的序列,有多少个区间,最大值减最小值小于k。
题解:发现这是一道区间查询最值的题目,所以用rmq肯定是没错的(事实上好像不用),枚举子串的左端点i,二分右端点R,使得[i,R]长度最大,那么这样一来这个子串是肯定符合的,实际上把右端点往左缩一个得到的小一个单位的子串也肯定是符合的,这样可以一直缩到区间变成[i,i],因此每一次枚举得到的区间[i,R]可以产生R−i+1个符合题意的子串,然后记得开long long就行了。
参考代码:
#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int a[200010],n,m,dp1[200010][30],dp2[100010][30],t;int rmq(int x,int y){int k=0;while ((1<<(k+1))<=y-x+1) k++;return max(dp1[x][k],dp1[y-(1<<k)+1][k])-min(dp2[x][k],dp2[y-(1<<k)+1][k]);}int main(){scanf("%d",&t);while(t--){scanf("%d%d",&n,&m);for (int i=1; i<=n; i++){scanf("%d",&a[i]);}for (int i=1; i<=n; i++){dp1[i][0]=a[i];dp2[i][0]=a[i];}for (int j=1; (1<<j)<=n; j++){for (int i=1; i+(1<<j)-1<=n; i++){dp1[i][j]=max(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]);dp2[i][j]=min(dp2[i][j-1],dp2[i+(1<<(j-1))][j-1]);}}int k=1;long long sum=0;for (int i=1; i<=n; i++){while (rmq(k,i)>=m&&k<i) k++;sum+=(i-k+1);}printf("%lld\n",sum);}return 0;}
题意:给你n个数,然后给你一对l,r,求这n个数的子区间中gcd值等于gcd(l,l+1,...,r)的个数。
题解:用线段树能求出一个区间的gcd,因此难点就在预处理出所有子区间以供筛选,一开始这个问题十分头疼,于是看了一篇tj,发现可以用map存储gcd的个数和种类(stl还是不熟),以a[i]为右端点进行扫描,右端点不断向后移动,以a[i]为右端点的gcd=gcd(以a[i-1]为右端点的gcd,a[i]),我们通过记录以前一个元素为右端点的gcd,然后扫描就可以了,这样就可以求出所有子区间的gcd种类和个数。
参考代码:
#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<map>#include<queue>using namespace std;struct node{long long num,l,r;}tree[4000010];map<long long,long long>mp1,mp2,cnt;map<long long,long long>::iterator it;long long a[1000010];long long gcd(long long a,long long b){return b?gcd(b,a%b):a;}long long lcm(long long a,long long b){return a/gcd(a,b)*b;}long long powmod(long long a,long long b,long long MOD){long long ans=1;while(b){if(b%2)ans=ans*a%MOD;a=a*a%MOD;b/=2;}return ans;}double dpow(double a,long long b){double ans=1.0;while(b){if(b%2)ans=ans*a;a=a*a;b/=2;}return ans;}void build(int i,int L,int R){tree[i].l=L;tree[i].r=R;if (L==R){tree[i].num=a[L];}else{int mid=(L+R)/2;build(i*2,L,mid);build(i*2+1,mid+1,R);tree[i].num=gcd(tree[i*2].num,tree[i*2+1].num);}}long long query(int i,int L,int R){if(tree[i].l==L&&tree[i].r==R){return tree[i].num;}if (R<=tree[i*2].r) return query(i*2,L,R);elseif (L>=tree[i*2+1].l) return query(i*2+1,L,R);else{int mid=(tree[i].l+tree[i].r)/2;return gcd(query(i*2,L,mid),query(i*2+1,mid+1,R));}}int main(){int n,m,t,icase=0;scanf("%d",&t);while (t--){scanf("%d",&n);for (int i=1; i<=n; i++)scanf("%lld",&a[i]);build(1,1,n);mp1.clear();mp2.clear();cnt.clear();for (int i=1; i<=n; i++){cnt[a[i]]++;mp2[a[i]]++;for (it=mp1.begin(); it!=mp1.end(); it++){long long k=gcd(a[i],it->first);cnt[k]+=it->second;mp2[k]+=it->second;}mp1.clear();for (it=mp2.begin(); it!=mp2.end(); it++){mp1[it->first]=it->second;}mp2.clear();}scanf("%d",&m);printf("Case #%d:\n",++icase);for (int i=1; i<=m; i++){int L,R;scanf("%d%d",&L,&R);long long ans=query(1,L,R);printf("%lld %lld\n",ans,cnt[ans]);}}return 0;}
题意:给出一个序列,有若干个询问,每次询问从l~r的这段区间内第k大的数是哪个。
题解:静态第k大问题。kuangbin的模板里面用主席树处理这个问题,但是kuangbin聚聚的模板本鶸并不能看懂,于是上网找了一个模板来用,这道题是一道裸模板题所以只要搞懂了主席树就套版就行了。
参考代码:
#include <iostream>#include <cstdio>#include <algorithm>using namespace std;struct Node{int a,b,rs,ls,sum;}tr[2000010];int a[100010],b[100010];int rt[100010],pos,cnt;void Build(int &node,int a,int b){node=++cnt;tr[node].a=a;tr[node].b=b;if(a==b)return;int mid=(a+b)>>1;Build(tr[node].ls,a,mid);Build(tr[node].rs,mid+1,b);}void Insert(int pre,int &node){node=++cnt;tr[node].ls=tr[pre].ls;tr[node].rs=tr[pre].rs;tr[node].a=tr[pre].a;tr[node].b=tr[pre].b;tr[node].sum=tr[pre].sum+1;if(tr[node].a==tr[node].b)return;int mid=(tr[node].a+tr[node].b)>>1;if(mid>=pos)Insert(tr[pre].ls,tr[node].ls);else Insert(tr[pre].rs,tr[node].rs);}int Query(int pre,int node,int k){if(tr[node].ls==tr[node].rs)return b[tr[node].a];int cmp=tr[tr[node].ls].sum-tr[tr[pre].ls].sum;if(cmp>=k)return Query(tr[pre].ls,tr[node].ls,k);else return Query(tr[pre].rs,tr[node].rs,k-cmp);}int main(){int n,q;scanf("%d%d",&n,&q);for(int i=1;i<=n;b[i]=a[i],i++)scanf("%d",&a[i]);sort(b+1,b+n+1);Build(rt[0],1,n);for(int i=1;i<=n;i++){pos=lower_bound(b+1,b+n+1,a[i])-b;Insert(rt[i-1],rt[i]);}int l,r,k;for(int i=1;i<=q;i++){scanf("%d%d%d",&l,&r,&k);printf("%d\n",Query(rt[l-1],rt[r],k));}return 0;}