【浮*光】#数据结构# 数据结构の相关练习题
那些年,我们做过的数据结构题...
T1:【p3792】由乃与大母神原型
- 1.单点修改;2.查询区间l、r是否可以重排为值域上连续的一段。
线段树维护区间min、区间max、区间和、区间平方和。
通过min和max算出,如果是连续段、‘和’和‘平方和’应该是多少。
类似hash的思想。但平方和可能被卡,可以用立方和处理。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p3792】由乃...(lxl果然毒瘤...) 1.单点修改;2.查询区间l、r是否可以重排为值域上连续的一段。*/ //线段树维护区间min、区间max、区间和、区间平方和。 //通过min和max算出,如果是连续段、‘和’和‘平方和’应该是多少。 //类似hash的思想。但平方和可能被卡,可以用立方和处理。 void reads(ll &x){ //读入优化(正负整数) ll f=1;x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; //正负号 } struct node{ double max; ll ans; }seg[500019]; const ll N=500019,mod=1000000007; ll n,m,op,x,y,a[N]; inline ll cube(ll x){return x*x%mod*x%mod;} //立方 inline ll sqr(ll x){return x*x%mod;} //平方 ll Min[N<<2],Sum[N<<2]; //区间最小值,区间立方和 inline void push_up(ll rt) { Min[rt]=min(Min[rt<<1],Min[rt<<1|1]),Sum[rt]=(Sum[rt<<1]+Sum[rt<<1|1])%mod; } inline void build(ll rt,ll l,ll r){ if(l==r){Min[rt]=a[l],Sum[rt]=cube(a[l]);return;} ll mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); push_up(rt); } inline void update(ll rt,ll l,ll r,ll p,ll x){ if(l==r){Min[rt]=x,Sum[rt]=cube(x);return;} ll mid=(l+r)>>1; if(p<=mid) update(rt<<1,l,mid,p,x); else update(rt<<1|1,mid+1,r,p,x); push_up(rt); } inline ll Query_Min(ll rt,ll l,ll r,ll ql,ll qr){ if(l>=ql&&r<=qr) return Min[rt]; ll mid=(l+r)>>1; if(qr<=mid) return Query_Min(rt<<1,l,mid,ql,qr); else if(ql>mid) return Query_Min(rt<<1|1,mid+1,r,ql,qr); else return min(Query_Min(rt<<1,l,mid,ql,qr),Query_Min(rt<<1|1,mid+1,r,ql,qr)); } inline ll Query_Sum(ll rt,ll l,ll r,ll ql,ll qr){ if (l>=ql&&r<=qr) return Sum[rt]; ll mid=(l+r)>>1; if (qr<=mid) return Query_Sum(rt<<1,l,mid,ql,qr); else if (ql>mid) return Query_Sum(rt<<1|1,mid+1,r,ql,qr); else return (Query_Sum(rt<<1,l,mid,ql,qr)+Query_Sum(rt<<1|1,mid+1,r,ql,qr))%mod; } ll sum2[N],sum3[N]; //前缀平方、立方和,便于比较 int main(){ reads(n),reads(m); for(ll i=1;i<=n;i++) reads(a[i]); build(1,1,n); //↓↓预处理区间前缀平方、立方和,便于比较 for(int i=1;i<=n;i++) sum2[i]=(sum2[i-1]+sqr(i))%mod, sum3[i]=(sum3[i-1]+cube(i))%mod; while(m--){ reads(op),reads(x),reads(y); if(op==1){ update(1,1,n,x,y); continue; } ll Minn=Query_Min(1,1,n,x,y),Summ=Query_Sum(1,1,n,x,y); ll Sums=((y-x+1)*cube(Minn)%mod+sum3[y-x] //连续段的立方和 +(y-x+1)*(y-x)/2%mod*3*Minn%mod*Minn%mod+sum2[y-x]*3%mod*Minn%mod)%mod; (Sums==Summ)?puts("damushen"):puts("yuanxing"); } }
T2:【uva1400】 "Ray, Pass me the dishes!"
- 一个长度为n的整数序列D,对m个询问做出回答。
- 对于询问(a,b),需要找到两个下标x和y,
- 使得a<=x<=y<=b,并且Dx+Dx+1+....+Dy尽量大。
- 如果有多组满足条件的x和y,x、y尽量小。
前缀max rt.pre=max(ls.pre,ls.sum+rs.pre);
后缀max rt.suf=max(rs.suf,rs.sum+ls.suf);
子段和 rt.sub=max(max(ls.sub,rs.sub),ls.suf+rs.pre);
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long LL; //防止重名 /*【uva1400】Ray??? 一个长度为n的整数序列D,对m个询问做出回答。 对于询问(rt,b),需要找到两个下标x和y, 使得a<=x<=y<=b,并且Dx+Dx+1+....+Dy尽量大。 如果有多组满足条件的x和y,x、y尽量小。*/ //前缀max rt.pre=max(ls.pre,ls.sum+rs.pre); //后缀max rt.suf=max(rs.suf,rs.sum+ls.suf); //子段和 rt.sub=max(max(ls.sub,rs.sub),ls.suf+rs.pre); const LL N=5e5+19; LL n,m,d[N],kase; struct Tree{ LL pre,suf,sub,sum,lch,rch,ll,rr; }tree[N<<2]; //最后面的四个值表示:子段和左端点,右端点,前缀和右端点,后缀和左端点 void init(){ memset(d,0,sizeof(d)),memset(tree,0,sizeof(tree)); } void push_up(Tree &rt,Tree ls,Tree rs){ //要分情况讨论,记录更新llrr等信息 rt.pre=rt.suf=rt.sub=rt.sum=0; rt.sum=ls.sum+rs.sum; if((ls.sum+rs.pre)<=ls.pre) rt.pre=ls.pre,rt.ll=ls.ll; else rt.pre=ls.sum+rs.pre,rt.ll=rs.ll; if((rs.sum+ls.suf)<=rs.suf) rt.suf=rs.suf,rt.rr=rs.rr; else rt.suf=rs.sum+ls.suf,rt.rr=ls.rr; if((ls.sub>=rs.sub)&&(ls.sub>=ls.suf+rs.pre)) rt.sub=ls.sub,rt.lch=ls.lch,rt.rch=ls.rch; else if((ls.suf+rs.pre>=ls.sub)&&(ls.suf+rs.pre>=rs.sub)) rt.sub=ls.suf+rs.pre,rt.lch=ls.rr,rt.rch=rs.ll; else rt.sub=rs.sub,rt.lch=rs.lch,rt.rch=rs.rch; } void build(LL rt,LL l,LL r){ if(l==r){ tree[rt].pre=tree[rt].suf=tree[rt].sub=tree[rt].sum=d[l]; tree[rt].ll=tree[rt].rr=tree[rt].lch=tree[rt].rch=l; return; } LL mid=(l+r)>>1; build(rt<<1,l,mid),build(rt<<1|1,mid+1,r); push_up(tree[rt],tree[rt<<1],tree[rt<<1|1]); } Tree query(LL rt,LL l,LL r,LL x,LL y){ Tree ls,rs,w; LL pd1=0,pd2=0,mid; if(l>=x&&r<=y) return tree[rt]; mid=(l+r)>>1; if(x<=mid) ls=query(rt<<1,l,mid,x,y),pd1=1; if(y>mid) rs=query(rt<<1|1,mid+1,r,x,y),pd2=1; if(pd1&&pd2) push_up(w,ls,rs); //统计区间 else if(pd1) w=ls; else if(pd2) w=rs; return w; } int main(){ freopen("test.in","r",stdin); freopen("test.out","w",stdout); while(~scanf("%lld%lld",&n,&m)){ init(),printf("Case %lld:\n",++kase); for(LL i=1;i<=n;i++) scanf("%lld",&d[i]); build(1,1,n); //建树,并push_up for(LL i=1,a,b;i<=m;i++){ Tree c; scanf("%lld%lld",&a,&b),c=query(1,1,n,a,b); printf("%lld %lld\n",c.lch,c.rch); } } }
T3:【p4198】楼房重建
- 每天改变某楼的高度,问每天能看见的楼数。
线段树维护斜率(递增序列)。即:维护区间max高度 和 区间答案(可以看见的个数)。
如何合并子区间?已知区间第一项和区间max,根据左区间的情况,确定右区间的贡献。
即:Seg[rt].ans=左儿子的ans+计算一下右儿子能做出的贡献。
如何计算右儿子贡献?递归,与左边maxx值比较,分类统计。
注意一些细节中,利用了上升序列的性质,减少了很多的合并时间。
这个题的细节处理很好,有很多巧妙的操作~ 差不多就是,注意左右区间最值合并时的性质。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p4198】楼房重建 每天改变某楼的高度,问每天能看见的楼数。*/ //线段树维护斜率(递增)。即:维护区间max高度,区间答案(可以看见的个数)。 //如何合并子区间?已知区间第一项和区间max,根据左区间的情况,确定右区间的贡献。 //即:Seg[rt].ans=左儿子的ans+计算一下右儿子能做出的贡献。 //如何计算右儿子贡献?递归,与左边maxx值比较,分类统计。 void reads(int &x){ //读入优化(正负整数) int f=1;x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; //正负号 } struct node{ double max; int ans; }seg[500019]; int calc_(int rt,double maxx,int l,int r){ //↓↓递归到叶子 int mid=(l+r)>>1; if(l==r) return seg[rt].max>maxx?1:0; if(seg[rt].max<=maxx) return 0; //无答案的区间 if(seg[rt<<1].max<=maxx) return calc_(rt<<1|1,maxx,mid+1,r); //↑↑只有此节点(原来的右儿子节点)中的右子树中有答案 else return calc_(rt<<1,maxx,l,mid)+seg[rt].ans-seg[rt<<1].ans; //↑↑左边的答案+右边能大于左边的答案(整体区间可行-左边区间可行) } void update(int rt,int l,int r,int x,double k){ //位置、斜率 if(l==r&&l==x){ seg[rt].max=k,seg[rt].ans=1; return; } int mid=(l+r)>>1; if(x<=mid) update(rt<<1,l,mid,x,k); else update(rt<<1|1,mid+1,r,x,k); //递归左右儿子 seg[rt].max=max(seg[rt<<1].max,seg[rt<<1|1].max); seg[rt].ans=seg[rt<<1].ans+calc_(rt<<1|1,seg[rt<<1].max,mid+1,r); } int main(){ int n,m,x,y; reads(n),reads(m); for(int i=1;i<=m;i++){ //↓↓修改位置 和 修改值(斜率) reads(x),reads(y),update(1,1,n,x,y*1.0/x); printf("%d\n",seg[1].ans); //总答案 } }
T4:【p5094】狂欢节
- 求n*(n-1)/2对奶牛的max(vi,vj)×dis(i,j)之和。
树状数组维护:
int num=query_num(a[i].pos); //坐标小于xi的奶牛的数量
int sum=query_sum(a[i].pos); //坐标小于xi的奶牛的坐标之和
同时记录一个all,表示前面所有坐标之和。因为已知i,所以可以统计为:
在i前面所有坐标小于xi的奶牛: s1=xi∗num−sum。
在i前面所有坐标大于xi的奶牛: s2=(all−sum)−(i−1−num)∗xi。
#include <bits/stdc++.h> #define int long long using namespace std; //【p5094】狂欢节 //树状数组 // https://www.luogu.org/blog/top-oier/solution-p5094 const int MAXN=20019; struct cow{ int voice,pos; }a[MAXN]; int n; bool cmp(cow x,cow y){ return x.voice<y.voice; } int c1[MAXN],c2[MAXN]; inline int lowbit(int x){ return x&(-x); } int query_num(int p) { int res=0; for(;p;p-=lowbit(p)) res+=c1[p]; return res; } int query_sum(int p) { int res=0; for(;p;p-=lowbit(p)) res+=c2[p]; return res; } void add_num(int p,int x){ for(;p<MAXN;p+=lowbit(p)) c1[p]+=x; } void add_sum(int p,int x){ for(;p<MAXN;p+=lowbit(p)) c2[p]+=x; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i].voice>>a[i].pos; sort(a+1,a+n+1,cmp); int ans=0,all=0; for(int i=1;i<=n;i++){ //树状数组维护: int num=query_num(a[i].pos); //坐标小于xi的奶牛的数量 int sum=query_sum(a[i].pos); //坐标小于xi的奶牛的坐标之和 ans+=(num*a[i].pos-sum)*a[i].voice; ans+=((all-sum)-(i-1-num)*a[i].pos)*a[i].voice; add_num(a[i].pos,1),add_sum(a[i].pos,a[i].pos); all+=a[i].pos; //all表示x前所有牛的坐标之和(大于,小于xi) } cout<<ans<<endl; return 0; //O(nlogn) }
T5:【uva11992】快速矩阵操作
有一个r行c列的全0矩阵,有以下三种操作。
- 1 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素加v。
- 2 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素变为v。
- 3 X1 Y1 X2 Y2 查询子矩阵(X1,Y1,X2,Y2)的和,最大值,最小值。
子矩阵(X1,Y1,X2,Y2)即满足X1<=X<=X2 Y1<=Y<=Y2的所有元素(X1,Y2)。
输入保证和不超过10^9。数据范围:r <= 20。
【分析】由数据范围可知,可以建立r棵线段树,在每棵上面维护区间。
注意:有set_(替换)和add_(增加)两个标记,但set_优先于add_。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long LL; //防止重名 /*【uva11992】快速矩阵操作 有一个r行c列的全0矩阵,有以下三种操作。 1 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素加v。 2 X1 Y1 X2 Y2 v 子矩阵(X1,Y1,X2,Y2)的元素变为v。 3 X1 Y1 X2 Y2 查询子矩阵(X1,Y1,X2,Y2)的和,最大值,最小值。 子矩阵(X1,Y1,X2,Y2)即满足X1<=X<=X2 Y1<=Y<=Y2的所有元素(X1,Y2)。 输入保证和不超过10^9。数据范围:r <= 20。*/ //【分析】由数据范围可知,可以建立r棵线段树,在每棵上面维护区间。 // 注意,有set_和add_两个标记,set_优先于add_。 void reads(int &x){ //读入优化(正负整数) int fx_=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx_; //正负号 } const int N=10000019; int r,c,m,tot=0,tree[22]; struct Tree{ int maxx,minn,sum,ls,rs,add,set; }seg[N]; void push_up(int rt){ seg[rt].sum=seg[seg[rt].ls].sum+seg[seg[rt].rs].sum; seg[rt].minn=min(seg[seg[rt].ls].minn,seg[seg[rt].rs].minn); seg[rt].maxx=max(seg[seg[rt].ls].maxx,seg[seg[rt].rs].maxx); } void push_down(int rt,int l,int r){ int mid=(l+r)>>1; if(seg[rt].set!=0){ //先放set标记 seg[seg[rt].ls].set=seg[seg[rt].rs].set=seg[rt].set; seg[seg[rt].ls].sum=seg[rt].set*(mid-l+1); seg[seg[rt].rs].sum=seg[rt].set*(r-mid); seg[seg[rt].ls].maxx=seg[seg[rt].rs].maxx=seg[rt].set; seg[seg[rt].ls].minn=seg[seg[rt].rs].minn=seg[rt].set; seg[seg[rt].ls].add=seg[seg[rt].rs].add=seg[rt].set=0; } if(seg[rt].add!=0){ //再放add标记 seg[seg[rt].ls].sum+=seg[rt].add*(mid-l+1); seg[seg[rt].rs].sum+=seg[rt].add*(r-mid); seg[seg[rt].ls].maxx+=seg[rt].add,seg[seg[rt].rs].maxx+=seg[rt].add; seg[seg[rt].ls].minn+=seg[rt].add,seg[seg[rt].rs].minn+=seg[rt].add; seg[seg[rt].ls].add+=seg[rt].add, //注意:加法标记不能直接继承,要+ seg[seg[rt].rs].add+=seg[rt].add,seg[rt].add=0; } } int build(int l,int r){ int rt=++tot; seg[rt].add=seg[rt].set=0; seg[rt].sum=seg[rt].maxx=seg[rt].minn=0; //多组数据 if(l==r){ seg[rt].sum=seg[rt].maxx=seg[rt].minn=0; seg[rt].ls=seg[rt].rs=0; return rt; } int mid=(l+r)>>1; seg[rt].ls=build(l,mid); //动态开点 seg[rt].rs=build(mid+1,r); push_up(rt); return rt; } void _add(int rt,int l,int r,int ql,int qr,int v){ if(ql<=l&&qr>=r){ seg[rt].add+=v,seg[rt].sum+=v*(r-l+1), seg[rt].maxx+=v,seg[rt].minn+=v; return; } push_down(rt,l,r); int mid=(l+r)>>1; if(ql<=mid) _add(seg[rt].ls,l,mid,ql,qr,v); if(mid<qr) _add(seg[rt].rs,mid+1,r,ql,qr,v); push_up(rt); } void _set(int rt,int l,int r,int ql,int qr,int v){ if(ql<=l&&qr>=r){ seg[rt].add=0,seg[rt].set=v, //add标记要清零 seg[rt].sum=v*(r-l+1),seg[rt].maxx=v,seg[rt].minn=v; return; } push_down(rt,l,r); int mid=(l+r)>>1; if(ql<=mid) _set(seg[rt].ls,l,mid,ql,qr,v); if(mid<qr) _set(seg[rt].rs,mid+1,r,ql,qr,v); push_up(rt); } int sum_query(int rt,int l,int r,int ql,int qr){ if(ql<=l&&qr>=r) return seg[rt].sum; push_down(rt,l,r); int mid=(l+r)>>1,ans=0; //注意ans初始值的设定 if(ql<=mid) ans+=sum_query(seg[rt].ls,l,mid,ql,qr); if(qr>mid) ans+=sum_query(seg[rt].rs,mid+1,r,ql,qr); return ans; } int max_query(int rt,int l,int r,int ql,int qr){ if(ql<=l&&qr>=r) return seg[rt].maxx; push_down(rt,l,r); int mid=(l+r)>>1,ans=-2e9; //注意ans初始值的设定 if(ql<=mid) ans=max(ans,max_query(seg[rt].ls,l,mid,ql,qr)); if(qr>mid) ans=max(ans,max_query(seg[rt].rs,mid+1,r,ql,qr)); return ans; } int min_query(int rt,int l,int r,int ql,int qr){ if(ql<=l&&qr>=r) return seg[rt].minn; push_down(rt,l,r); int mid=(l+r)>>1,ans=2e9; //注意ans初始值的设定 if(ql<=mid) ans=min(ans,min_query(seg[rt].ls,l,mid,ql,qr)); if(qr>mid) ans=min(ans,min_query(seg[rt].rs,mid+1,r,ql,qr)); return ans; } int main(){ while(~scanf("%d%d%d",&r,&c,&m)){ //↓↓每行一棵线段树,tree记录rt编号 tot=0; for(int i=1;i<=r;i++) tree[i]=build(1,c); for(int i=1,op,x1,x2,yi,y2,v;i<=m;i++){ reads(op),reads(x1),reads(yi),reads(x2),reads(y2); if(op==1){ reads(v); //在 维护x1~x2行的线段树上 修改 for(int i=x1;i<=x2;i++) _add(tree[i],1,c,yi,y2,v); } if(op==2){ reads(v); //在 维护x1~x2行的线段树上 赋值 for(int i=x1;i<=x2;i++) _set(tree[i],1,c,yi,y2,v); } if(op==3){ int sum_=0,min_=(int)2e9,max_=(int)-2e9; for(int i=x1;i<=x2;i++){ sum_+=sum_query(tree[i],1,c,yi,y2); min_=min(min_,min_query(tree[i],1,c,yi,y2)); max_=max(max_,max_query(tree[i],1,c,yi,y2)); } printf("%d %d %d\n",sum_,min_,max_); } } } }
T6:【CF833B】The Bakery
- 将一个长度为n的序列分为k段,使得总价值最大。
- 一段区间的价值表示为区间内不同数字的个数。
【分析】dp[j][i]=max{dp[j-1][k]+cnt[k+1][i]}
dp[i][j]意为1~i之间分割j次所产生的最大值;cnt[i][j]表示i-j之间不同的颜色个数。
看到max可以考虑线段树优化,即:需要在O(logn)的时间内计算出cnt[i][j]。
可知:一种颜色能够产生贡献的范围 = 他上一次出现的位置 ~ 他当前的位置。
此颜色如果在范围内,产生1贡献,所以可以使用线段树区间加,给pre[i]~i都加上1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> #include<set> using namespace std; typedef long long ll; /*【CF833B】The Bakery 将一个长度为n的序列分为k段,使得总价值最大。 一段区间的价值表示为区间内不同数字的个数。 */ /*【分析】dp[j][i]=max{dp[j-1][k]+cnt[k+1][i]} dp[i][j]意为1~i之间分割j次所产生的最大值;cnt[i][j]表示i-j之间不同的颜色个数。 看到max可以考虑线段树优化,即:需要在O(logn)的时间内计算出cnt[i][j]。 可知:一种颜色能够产生贡献的范围 = 他上一次出现的位置 ~ 他当前的位置。 此颜色如果在范围内,产生1贡献,所以可以使用线段树区间加,给pre[i]~i都加上1。*/ void reads(int &x){ //读入优化(正负整数) int fx_=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx_; //正负号 } #define ls rt<<1 #define rs rt<<1|1 struct node{ int l,r,sum,tag; }seg[1500019]; int dp[59][40000],pre[40000],pos[40000]; void init(){ memset(seg,0,sizeof(seg)); } void push_up(int rt) { seg[rt].sum=max(seg[ls].sum,seg[rs].sum); } void push_down(int rt){ seg[ls].sum+=seg[rt].tag,seg[ls].tag+=seg[rt].tag; seg[rs].sum+=seg[rt].tag,seg[rs].tag+=seg[rt].tag,seg[rt].tag=0; } void build(int rt,int l,int r,int now){ if(l==r){ seg[rt].l=l,seg[rt].r=r; seg[rt].sum=dp[now][l-1]; return; } seg[rt].l=l,seg[rt].r=r; int mid=(l+r)>>1; build(ls,l,mid,now),build(rs,mid+1,r,now),push_up(rt); } void update(int rt,int l,int r,int val){ if(seg[rt].l==l&&seg[rt].r==r) { seg[rt].sum+=val,seg[rt].tag+=val; return; } if(seg[rt].tag) push_down(rt); int mid=(seg[rt].l+seg[rt].r)>>1; if(mid<l) update(rs,l,r,val); else if(mid>=r) update(ls,l,r,val); else update(ls,l,mid,val),update(rs,mid+1,r,val); push_up(rt); } int query(int rt,int l,int r){ if(seg[rt].l==l&&seg[rt].r==r) return seg[rt].sum; if(seg[rt].tag) push_down(rt); int mid=(seg[rt].l+seg[rt].r)>>1; if(mid<l) return query(rs,l,r); else if(mid>=r) return query(ls,l,r); else return max(query(ls,l,mid),query(rs,mid+1,r)); } int main(){ int n,k,t; reads(n),reads(k); //↓↓记录上一次出现的位置 for(int i=1;i<=n;i++) reads(t),pre[i]=pos[t]+1,pos[t]=i; for(int i=1;i<=k;i++){ //分成k段 init(); build(1,1,n,i-1); //每次都要根据上一段dp值重新建树 for(int j=1;j<=n;j++) //dp[i][now]=max{dp[i-1][las]+cnt[now][las+1]} update(1,pre[j],j,1),dp[i][j]=query(1,1,j); } printf("%d\n",dp[k][n]); return 0; }
T7:【p3939】数颜色
- 1 lj rj cj :询问在区间[lj,rj]里有多少只颜色为cj的兔子;
- 2 xj: xj 和 xj+1 两只兔子交换了位置。
主席树维护区间历史版本的信息,对于操作2:
update(rt[k],rt[k],1,MAX,col[k],-1),update(rt[k],rt[k],1,MAX,col[k+1],1),
update(rt[k+1],rt[k+1],1,MAX,col[k+1],-1),update(rt[k+1],rt[k+1],1,MAX,col[k],1),
↑↑ 注意,k+1处统计的是1~k+1的信息,k与k+1替换了,并不改变k+1的前缀信息
然后再 swap(col[k],col[k+1]); 即:在主席树的历史版本k处改变col[k],col[k+1]的个数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p3939】数颜色 1 lj rj cj :询问在区间[lj,rj]里有多少只颜色为cj的兔子; 2 xj: xj 和 xj+1 两只兔子交换了位置。*/ void reads(int &x_){ //读入优化(正负整数) int fx_=1;x_=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();} while(s>='0'&&s<='9'){x_=(x_<<3)+(x_<<1)+s-'0';s=getchar();} x_*=fx_; //正负号 } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int N=20000019,MAX=300000; int n,m,node_cnt=0,col[500019],sum[N],rt[N],ls[N],rs[N]; void update(int las_,int &now_,int l,int r,int val,int op){ now_=++node_cnt; //主席树动态开点:寻找新值val的位置,建出这条新链 ls[now_]=ls[las_],rs[now_]=rs[las_],sum[now_]=sum[las_]+op; if(l==r) return; int mid=(l+r)>>1; if(val<=mid) update(ls[las_],ls[now_],l,mid,val,op); else update(rs[las_],rs[now_],mid+1,r,val,op); } int query(int u,int v,int l,int r,int k){ //查询区间中颜色k的个数 if(l==r) return sum[v]-sum[u]; int mid=(l+r)>>1; if(k<=mid) return query(ls[u],ls[v],l,mid,k); else return query(rs[u],rs[v],mid+1,r,k); //递归子树寻找位置k } int main(){ reads(n),reads(m); for(int i=1;i<=n;i++) reads(col[i]),update(rt[i-1],rt[i],1,MAX,col[i],1); for(int i=1,op,l,r,k;i<=m;i++){ reads(op); if(op==1) reads(l),reads(r),reads(k), //寻找区间颜色k的个数 write(query(rt[l-1],rt[r],1,MAX,k)),puts(""); if(op==2) reads(k),update(rt[k],rt[k],1,MAX,col[k],-1), update(rt[k],rt[k],1,MAX,col[k+1],1), //update(rt[k+1],rt[k+1],1,MAX,col[k+1],-1), //update(rt[k+1],rt[k+1],1,MAX,col[k],1), //↑↑注意,k+1处统计的是1~k+1的信息,k与k+1替换了,并不改变k+1的前缀信息 swap(col[k],col[k+1]); //在主席树的历史版本k处改变col[k/k+1]的个数 } }
T8:【sp3946】kth num
- 查询区间kth(主席树模板题)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; //【sp3946】kth num void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=200019; int node_cnt,n,m,sum[N*30],rt[N],lc[N*30],rc[N*30]; int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名 void build(int &rt,int l,int r){ rt=++node_cnt; if(l==r) return; int mid=(l+r)>>1; build(lc[rt],l,mid),build(rc[rt],mid+1,r); } // ↑↑ 注意动态开点的建树方法,递归建立节点 lc[rt]、rc[rt]。 int modify(int _rt,int l,int r){ //在_rt的基础上,新建一条链now_ int now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置 lc[now_]=lc[_rt],rc[now_]=rc[_rt],sum[now_]=sum[_rt]+1; if(l==r) return now_; //已经递归到新树(链)的根 int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置 if(p<=mid) lc[now_]=modify(lc[now_],l,mid); else rc[now_]=modify(rc[now_],mid+1,r); return now_; //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号 } int query(int u,int v,int l,int r,int k){ //查询区间Kth int ans_id,mid=((l+r)>>1),x=sum[lc[v]]-sum[lc[u]]; //x:左边的个数 if(l==r) return l; //走到叶子节点,此时的编号(在排序后的数组中)就代表区间Kth if(x>=k) ans_id=query(lc[u],lc[v],l,mid,k); //左边个数足够,递归左子树 else ans_id=query(rc[u],rc[v],mid+1,r,k-x); return ans_id; } int main(){ int bn_,ans_id; reads(n),reads(m); for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i]; sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1; build(rt[0],1,bn_); //初始空树(便于线段树合并) for(int i=1;i<=n;i++){ p=lower_bound(b+1,b+bn_+1,a[i])-b; //二分查找a[i]在b中的排名 rt[i]=modify(rt[i-1],1,bn_); //在前一棵树的基础上,递归区间(1,bn_),加入一条链 } while(m--){ int l,r,k; reads(l),reads(r),reads(k); ans_id=query(rt[l-1],rt[r],1,bn_,k); printf("%d\n",b[ans_id]); } }
T9:【p3567】KUR
- 每次询问区间内有没有数的出现次数超过一半。
对于编号区间(l,r),对(1,n)的权值范围进行二分;
若左区间个数<=(r-l+1)/2,则去右区间...
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; //【p3567】KUR //每次询问区间内有没有数的出现次数超过一半 // 对于编号区间(l,r),对(1,n)的权值范围进行二分,若左区间个数<=(r-l+1)/2,则去右区间... void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=500019; int node_cnt,n,m,sum[N*30],rt[N],lc[N*30],rc[N*30]; int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名 void build(int &rt,int l,int r){ rt=++node_cnt; if(l==r) return; int mid=(l+r)>>1; build(lc[rt],l,mid),build(rc[rt],mid+1,r); } // ↑↑ 注意动态开点的建树方法,递归建立节点 lc[rt]、rc[rt]。 int modify(int las_,int l,int r){ //在las_的基础上,新建一条链now_ int now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置 lc[now_]=lc[las_],rc[now_]=rc[las_],sum[now_]=sum[las_]+1; if(l==r) return now_; //已经递归到新树(链)的根 int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置 if(p<=mid) lc[now_]=modify(lc[now_],l,mid); else rc[now_]=modify(rc[now_],mid+1,r); return now_; //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号 } int query(int u,int v,int l,int r,int len){ //查询区间中是否存在len个相同数字 if(l==r) return l; //走到(代表权值的)叶子节点,此时的权值就是答案(此时的l、r是数值) int mid=((l+r)>>1),x=sum[lc[v]]-sum[lc[u]],y=sum[rc[v]]-sum[rc[u]]; // x:左边的总个数(差分求值); y:右边的总个数 。(即:数值在范围内的数的个数) if(x>len) return query(lc[u],lc[v],l,mid,len); //因为是按数值划分的,左右区间不会相交 else if(y>len) return query(rc[u],rc[v],mid+1,r,len); else return 0; } int main(){ int bn_,ans; reads(n),reads(m); for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i]; sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1; build(rt[0],1,bn_); //初始空树(便于线段树合并) for(int i=1;i<=n;i++){ p=lower_bound(b+1,b+bn_+1,a[i])-b; //二分查找a[i]在b中的排名 rt[i]=modify(rt[i-1],1,bn_); //在前一棵树的基础上,递归区间(1,bn_),加入一条链 } while(m--){ int l,r; reads(l),reads(r); int len=(r-l+1)>>1; //需要相同的个数 ans=query(rt[l-1],rt[r],1,bn_,len); printf("%d\n",ans); } //bn_:离散化之后的n }
T10:【p2633】Count On A Tree
- N个节点的点权树,M个询问(u,v,k):u xor lastans ~ v 之间第K小的点权。
【思路】对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; //【p2633】Count On A Tree //主席树维护树上路径 + 树上差分 // N个节点的点权树,M个询问(u,v,k):u xor lastans ~ v 之间第K小的点权。 //【思路】对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。 void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=1000019; struct node{ int nextt,ver; }e[N*2]; int node_cnt,n,m,bn_,tot=1,head[N*2],sum[N*30],rt[N],lc[N*30],rc[N*30]; int siz[N*2],son[N*2],top[N*2],dep[N*2],fa[N*2],num[N*2]; int a[N],b[N],p; //原序列和离散序列,p:此点在总体中的排名 void add(int x,int y) { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; } //-------------------主席树----------------------// int modify(int las_,int &now_,int l,int r,int x){ now_=++node_cnt; //每次都要开新节点,复制已经记录的下层的位置 lc[now_]=lc[las_],rc[now_]=rc[las_],sum[now_]=sum[las_]+1; if(l==r) return now_; //已经递归到新树(链)的根 int mid=(l+r)>>1; //↓↓按左右子树不同,分类复制上次的位置 if(p<=mid) lc[now_]=modify(lc[las_],lc[now_],l,mid,x); else rc[now_]=modify(rc[las_],rc[now_],mid+1,r,x); return now_; //递归,处理得出此链上每个节点的左右儿子↑↑ 最终在主程序中返回rt[i]的编号 } int query(int u,int v,int lca,int lca_fa,int l,int r,int k){ //树上路径Kth if(l>=r) return l; //走到(代表权值的)叶子节点,此时的权值就是答案 int mid=(l+r)>>1,x=sum[lc[u]]+sum[lc[v]]-sum[lc[lca]]-sum[lc[lca_fa]]; if(x>=k) return query(lc[u],lc[v],lc[lca],lc[lca_fa],l,mid,k); else return query(rc[u],rc[v],rc[lca],rc[lca_fa],mid+1,r,k-x); } //---------------树链剖分(求lca??)-----------------// void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子 siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1; modify(rt[fa[u]],rt[u],1,bn_,a[u]); for(ll i=head[u];i;i=e[i].nextt){ if(e[i].ver==fa_) continue; dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子 } } void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值 if(son[u]){ //先走重儿子,使重链在线段树中的位置连续 top[son[u]]=top[u],dfs2(son[u],u); //更新top值 } for(ll i=head[u];i;i=e[i].nextt){ if(top[e[i].ver]) continue; //除去u的重儿子或父亲 top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点 } } int LCA(int x,int y){ while(top[x]!=top[y]) (dep[top[x]]>=dep[top[y]])?x=fa[top[x]]:y=fa[top[y]]; return (dep[x]>=dep[y])?y:x; } //-------------------主程序-----------------------// int main(){ int ans; reads(n),reads(m); for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i]; sort(b+1, b+n+1); bn_=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+bn_+1,a[i])-b; for(int i=1,u,v;i<n;i++) reads(u),reads(v),add(u,v),add(v,u); dfs1(1,0),top[1]=1,dfs2(1,0); //树链剖分求lca while(m--){ int x,y,z,lca; reads(x),reads(y),reads(z); x^=ans,lca=LCA(x,y); printf("%d\n",query(rt[x],rt[y],rt[lca],rt[fa[lca]],1,bn_,z)); } }
T11:【p3302】森林
- Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。
- 此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
- lc x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。
- 强制在线。即要按照 x^lastans y^lastans k^lastans 计算。
【思路】1.查询路径权值第k小; 2.连接两棵树。利用启发式合并(小的接到大的上面)。
dfs暴力合并,用父节点重建每个节点的主席树,并且更新每个节点的倍增数组(求lca)。
对于操作1,对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p3302】森林 Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。 此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。 lc x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。 强制在线。即要按照 x^lastans y^lastans k^lastans 计算。*/ /*【思路】1.查询路径权值第k小; 2.连接两棵树。利用启发式合并(小的接到大的上面)。 dfs暴力合并,用父节点重建每个节点的主席树,并且更新每个节点的倍增数组(求lca)。 对应的路径可以用树上差分表示:sum[l]+sum[r]−sum[lca]−sum[lca_fa]。*/ void reads(int &x_){ int fx_=1;x_=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x_=x_*10+ch_-'0';ch_=getchar();} x_*=fx_; } const int N=100019,M=5000019; int n,m,tot,q,bn_,ans,ver[N*4],nextt[N*4],head[N]; int a[N],fa[N],siz[N],b[N]; //注意这里fa[]记录并查集的联通情况 void add(int u,int v) //双向边 { ver[++tot]=v,nextt[tot]=head[u],head[u]=tot; ver[++tot]=u,nextt[tot]=head[v],head[v]=tot; } int lc[M],rc[M],sum[M],rt[N],cnt; //主席树sum[] void update(int las_,int &now_,int l,int r,int x){ sum[now_=++cnt]=sum[las_]+1; if(l==r) return; int mid=(l+r)>>1; if(x<=mid) rc[now_]=rc[las_],update(lc[las_],lc[now_],l,mid,x); else lc[now_]=lc[las_],update(rc[las_],rc[now_],mid+1,r,x); } int query(int u,int v,int lca,int lca_fa,int l,int r,int k){ int mid=(l+r)>>1; if(l>=r) return l; //找到了,返回编号 int x=sum[lc[v]]+sum[lc[u]]-sum[lc[lca]]-sum[lc[lca_fa]]; if(x>=k) return query(lc[u],lc[v],lc[lca],lc[lca_fa],l,mid,k); else return query(rc[u],rc[v],rc[lca],rc[lca_fa],mid+1,r,k-x); } int find_fa(int x){ return fa[x]==x?x:fa[x]=find_fa(fa[x]); } int f[N][17],dep[N],vis[N]; //vis数组用于初始建立森林 inline int get_rank(int x){ return lower_bound(b+1,b+bn_+1,x)-b; } void dfs(int u,int fa_,int root){ //启发式合并,全部合并到root为根的树上 f[u][0]=fa_; for(int i=1;i<=16;i++) f[u][i]=f[f[u][i-1]][i-1]; siz[root]++,dep[u]=dep[fa_]+1,fa[u]=root,vis[u]=1; update(rt[fa_],rt[u],1,bn_,get_rank(a[u])); //找到u在新树上的新排名 for(int i=head[u];i;i=nextt[i]) if(ver[i]!=fa_) dfs(ver[i],u,root); } int LCA(int x,int y){ if(x==y) return x; if(dep[x]<dep[y]) swap(x,y); for(int i=16;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i]; if(x==y) return x; for(int i=16;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; //倍增求lca } int main(){ int t; reads(t),reads(n),reads(m),reads(q); //t:当前测试组编号,无用 for(int i=1;i<=n;i++) reads(a[i]),b[i]=a[i],fa[i]=i; sort(b+1,b+1+n); bn_=unique(b+1,b+1+n)-b-1; //离散化 for(int i=1,u,v;i<=m;i++) reads(u),reads(v),add(u,v); for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0,i); //建出森林 while(q--){ char ch[19]; int x,y,k; cin>>ch,reads(x),reads(y),x=x^ans,y=y^ans; if(ch[0]=='Q'){ reads(k),k=k^ans; int lca=LCA(x,y); ans=b[query(rt[x],rt[y],rt[lca],rt[f[lca][0]],1,bn_,k)]; cout<<ans<<endl; } // ↑↑ f[lca][0]=fa_lca,在区间内找第k小的数 else{ add(x,y); int fx=find_fa(x),fy=find_fa(y); //启发式:小的合并到大的上 if(siz[fx]<siz[fy]) swap(x,y),swap(fx,fy); dfs(y,x,fx); } //dfs暴力合并 } }
其中启发式合并的代码:
inline int get_rank(int x){ return lower_bound(b+1,b+bn_+1,x)-b; } void dfs(int u,int fa_,int root){ //启发式合并,全部合并到root为根的树上 f[u][0]=fa_; for(int i=1;i<=16;i++) f[u][i]=f[f[u][i-1]][i-1]; siz[root]++,dep[u]=dep[fa_]+1,fa[u]=root; update(rt[fa_],rt[u],1,bn_,get_rank(a[u])); //找到u在新树上的新排名 for(int i=head[u];i;i=nextt[i]) if(ver[i]!=fa_) dfs(ver[i],u,root); }
T12:【p1600】天天爱跑步
将每个人的跑步路线拆成两段路径:s->lca,lca->t。
对于第一段经过的点,当前这个人能对它产生贡献当且仅当dep[s]-dep[i]==w[i];
对于第二段路径同理:能产生贡献当且仅当dep[t]-dep[i]==dis(s,t)-w[i]。
同时要用动态开点线段树维护,判断lca有没有被算重。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; //【p1600】天天爱跑步 /*【分析】将每个人的跑步路线拆成两段路径:s->lca,lca->t。 对于第一段经过的点,当前这个人能对它产生贡献当且仅当dep[s]-dep[i]==w[i]; 对于第二段路径同理:能产生贡献当且仅当dep[t]-dep[i]==dis(s,t)-w[i]。 同时要用动态开点线段树维护,判断lca有没有被算重。*/ void reads(int &x){ //读入优化(正负整数) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正负号 } const int N=300019; int n,m; int son[N],num[N],ans[N],head[N],top[N],dep[N],fa[N],siz[N]; struct Node{int l,r,sum;}T[N*30]; struct node{int ver,nextt;}e[N<<1]; int s[N],t[N],w[N],lca[N],dfn=0,tot=0,cnt=0,rt[N*3]; inline void add(int u,int v){e[++cnt].ver=v,e[cnt].nextt=head[u],head[u]=cnt;} inline void update(int&p,int l,int r,int k,int v){ if(!p) p=++tot,T[p].l=T[p].r=T[p].sum=0; T[p].sum+=v; if(l==r) return; int mid=l+r>>1; //动态开点 if(k<=mid) update(T[p].l,l,mid,k,v); else update(T[p].r,mid+1,r,k,v); } inline int query(int p,int l,int r,int ql,int qr){ if(!p)return 0; if(ql<=l&&r<=qr) return T[p].sum; int mid=l+r>>1; //查询区间sum if(qr<=mid) return query(T[p].l,l,mid,ql,qr); if(ql>mid)return query(T[p].r,mid+1,r,ql,qr); return query(T[p].l,l,mid,ql,mid)+query(T[p].r,mid+1,r,mid+1,qr); } inline void dfs1(int x){ siz[x]=1,son[x]=0; for(int i=head[x];i;i=e[i].nextt){ int v=e[i].ver; if(v==fa[x]) continue; fa[v]=x,dep[v]=dep[x]+1,dfs1(v),siz[x]+=siz[v]; if(siz[v]>siz[son[x]]) son[x]=v; } } //求fa[],dep[],siz[],son[] inline void dfs2(int x,int tp){ top[x]=tp,num[x]=++dfn; if(son[x]) dfs2(son[x],tp); for(int i=head[x];i;i=e[i].nextt){ int v=e[i].ver; if(v!=son[x]&&v!=fa[x]) dfs2(v,v); } } inline int LCA(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; } return dep[x]<dep[y]?x:y; } //用 轻重链划分 求x,y的lca int main(){ reads(n),reads(m); for(int i=1,u,v;i<n;++i) reads(u),reads(v),add(u,v),add(v,u); for(int i=1;i<=n;++i) reads(w[i]); dfs1(1),dfs2(1,1); //链剖 for(int i=1;i<=m;++i){ reads(s[i]),reads(t[i]); lca[i]=LCA(s[i],t[i]); if(dep[s[i]]-dep[lca[i]]==w[lca[i]]) ans[lca[i]]--; update(rt[dep[s[i]]],1,n,num[s[i]],1); //差分 if(fa[lca[i]]) update(rt[dep[s[i]]],1,n,num[fa[lca[i]]],-1); } for(int i=1;i<=n;++i) ans[i]+=query(rt[w[i]+dep[i]],1,n,num[i],num[i]+siz[i]-1); memset(rt,0,sizeof(rt)),tot=0; for(int i=1;i<=m;++i){ update(rt[n*2+dep[s[i]]-2*dep[lca[i]]],1,n,num[t[i]],1); if(fa[lca[i]]) update(rt[n*2+dep[s[i]]-2*dep[lca[i]]],1,n,num[fa[lca[i]]],-1); } for(int i=1;i<=n;++i) ans[i]+=query(rt[w[i]-dep[i]+n*2],1,n,num[i],num[i]+siz[i]-1); for(int i=1;i<=n;++i) cout<<ans[i]<<' '; return 0; }
T13:【p4215】踩气球
- 给出一个长度为 n 的序列,序列中每一个数都是正整数。
- m 个指定区间,q 次操作,每次操作将某个位置的数-1(最多减到0),
- 并询问有多少个指定区间的区间和为0。强制在线。
【思路】线段树维护:v[rt].push_back(id);
即:每个管理/叶子节点存一个vector,记录此点管理的所有位置有关的区间id。
c[id]++; 记录区间id影响的(受管理的)线段树区间数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p4215】踩气球 给出一个长度为 n 的序列,序列中每一个数都是正整数。 现在给出 m 个指定区间以及 q 次操作,每次操作将某个位置的数-1(最多减到0), 并询问有多少个指定区间的区间和为0。强制在线。*/ /*【思路】线段树维护:v[rt].push_back(id); 即:每个管理/叶子节点存一个vector,记录此点管理的所有位置有关的区间id。 c[id]++; 记录区间id影响的(受管理的)线段树区间数。 */ void reads(int &x_){ int fx_=1;x_=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx_=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x_=x_*10+ch_-'0';ch_=getchar();} x_*=fx_; } const int N=100019; #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 vector<int> v[N<<2]; ll sum[N<<2]; int c[N],ans; // c[]:区间id影响的(受管理的)线段树区间数 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("%lld",&sum[rt]); return; } int mid=(l+r)>>1; build(lson),build(rson),pushup(rt); } void init(int ql,int qr,int id,int l,int r,int rt){ if(ql<=l&&r<=qr){ v[rt].push_back(id),c[id]++; return; } int mid=(l+r)>>1; if(ql<=mid) init(ql,qr,id,lson); if(qr>mid) init(ql,qr,id,rson); //把每个区间影响的线段树节点都标上id } void solve(int p,int l,int r,int rt){ sum[rt]--; if(!sum[rt]){ //正好减到0 vector<int>::iterator it; //注意每个管理节点都要修改 for(it=v[rt].begin();it!=v[rt].end();it++) { c[*it]--; if(!c[*it]) ans++; } } if(l==r) return; int mid=(l+r)>>1; if(p<=mid) solve(p,lson); else solve(p,rson); } int main(){ int n,m,q,x,y; reads(n),reads(m); build(1,n,1); for(int i=1;i<=m;i++) reads(x),reads(y),init(x,y,i,1,n,1); reads(q); while(q--) reads(x), //单点-1 solve((x+ans-1)%n+1,1,n,1),printf("%d\n",ans); }
T14:【p1471】方差
- 操作1:1 x y k,表示将第x到第y项每项加k。
- 操作2:2 x y,表示求出第x到第y项的平均数。
- 操作3:3 x y,表示求出第x到第y项的方差。
【分析】方差的公式可以转化为:(a1^2+a2^2+...+an^2)/n-平均数^2。
所以只需要维护,区间平方和 和 区间和 即可。
修改操作:a1^2+a2^2+...+an^2 --> (a1+x)^2+(a2+x)^2+...+(an+x)^2
即:a1^2+a2^2+...+an^2 + n*(x^2) + 2*x*原平均数*n。
#include<cmath> #include<cstdio> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> #include<queue> #include<vector> #include<deque> using namespace std; typedef long long ll; /*【p1471】方差 操作1:1 x y k,表示将第x到第y项每项加上k,k为一实数。 操作2:2 x y,表示求出第x到第y项这一子数列的平均数。 操作3:3 x y,表示求出第x到第y项这一子数列的方差。*/ /*【分析】方差的公式可以转化为:(a1^2+a2^2+...+an^2)/n-平均数^2。 所以只需要维护,区间平方和 和 区间和 即可。 修改操作:a1^2+a2^2+...+an^2 --> (a1+x)^2+(a2+x)^2+...+(an+x)^2 即:a1^2+a2^2+...+an^2 + n*(x^2) + 2*x*原平均数*n。 */ struct node{ double a,b,tag; }tree[1500019]; void PushUp(int rt){ //向上求出管理节点 tree[rt].a=tree[rt<<1].a+tree[rt<<1|1].a; tree[rt].b=tree[rt<<1].b+tree[rt<<1|1].b; } //维护 区间所有数的和 和 区间所有数的平方和 void build(int l,int r,int rt){ //建立线段树 if(l==r){ cin>>tree[rt].a; //叶子节点 tree[rt].b=tree[rt].a*tree[rt].a; return; } int mid=(l+r)>>1; //递归左右儿子 build(l,mid,rt<<1),build(mid+1,r,rt<<1|1); PushUp(rt); //标记上移 } void PushDown(int rt,int len){ if(tree[rt].tag){ //标记每次下移一层 tree[rt<<1].b+=2*tree[rt].tag*tree[rt<<1].a +(len-len/2)*tree[rt].tag*tree[rt].tag; tree[rt<<1|1].b+=2*tree[rt].tag*tree[rt<<1|1].a +(len/2)*tree[rt].tag*tree[rt].tag; tree[rt<<1].a+=(len-len/2)*tree[rt].tag; tree[rt<<1|1].a+=(len/2)*tree[rt].tag; tree[rt<<1].tag+=tree[rt].tag; tree[rt<<1|1].tag+=tree[rt].tag; tree[rt].tag=0; //标记下移,并清空原标记 } } void update(int x,int y,double k,int l,int r,int rt){ if(x<=l&&r<=y){ tree[rt].tag+=k; tree[rt].b+=2*k*tree[rt].a+k*k*(r-l+1), tree[rt].a+=(r-l+1)*k; return; } PushDown(rt,r-l+1); //标记下移 int mid=(l+r)>>1; //↓↓判断此时左右区间是否和原区间有交集 if(mid>=x) update(x,y,k,l,mid,rt<<1); if(mid<y) update(x,y,k,mid+1,r,rt<<1|1); PushUp(rt); //修改这条线路上的sum值 } double query_1(int x,int y,int l,int r,int rt){ if(x<=l&&r<=y) return tree[rt].a; PushDown(rt,r-l+1); //标记下移 int mid=(l+r)>>1; double sums=0; if(mid>=x) sums+=query_1(x,y,l,mid,rt<<1); if(mid<y) sums+=query_1(x,y,mid+1,r,rt<<1|1); return sums; //求区间和 } double query_2(int x,int y,int l,int r,int rt){ if(x<=l&&r<=y) return tree[rt].b; PushDown(rt,r-l+1); //标记下移 int mid=(l+r)>>1; double sums=0; if(mid>=x) sums+=query_2(x,y,l,mid,rt<<1); if(mid<y) sums+=query_2(x,y,mid+1,r,rt<<1|1); return sums; //求区间平方和 } int main(/*hs_love_wjy*/){ int n,m,op,x,y; double k; scanf("%d%d",&n,&m); build(1,n,1); //建树,在建树时输入原数组值 for(int i=1;i<=m;i++){ scanf("%d",&op); //操作编号 if(op==1) scanf("%d%d%lf",&x,&y,&k), update(x,y,k,1,n,1); //区间(x,y)+k if(op==2) scanf("%d%d",&x,&y), //区间平均数 printf("%.4lf\n",query_1(x,y,1,n,1)/(y-x+1)); if(op==3){ scanf("%d%d",&x,&y); double cnt_b=query_2(x,y,1,n,1)/(y-x+1), cnt_a=query_1(x,y,1,n,1)/(y-x+1); cnt_b=cnt_b-cnt_a*cnt_a; //由公式转化而来 printf("%.4lf\n",cnt_b); } } }
T15:【p2253】好一个一中腰鼓
- 一个01串,每次要求对某个点进行异或修改,
- 求每次修改后的最长连续(0和1交错出现)序列长度。
l代表从线段树管理节点维护的左端点开始,向右的最大01序列的长度。
r代表从线段树管理节点维护的右端点开始,向左的最大01序列的长度。
ans代表整个区间最大的01序列(可以不含左,右端点)
lk代表区间左端点的颜色,rk代表区间右端点的颜色。
那么,区间上传(pushup)应该这样写:
void PushUp(int rt){ //向上求出管理节点 tree[rt].l=tree[rt<<1].l,tree[rt].r=tree[rt<<1|1].r; tree[rt].lk=tree[rt<<1].lk,tree[rt].rk=tree[rt<<1|1].rk; tree[rt].ans=max(tree[rt<<1].r,tree[rt<<1].ans); tree[rt].ans=max(tree[rt<<1|1].l,tree[rt].ans); tree[rt].ans=max(tree[rt].ans,tree[rt<<1|1].ans); if(tree[rt<<1].rk!=tree[rt<<1|1].lk){ //中间两节点可以合并,序列合并 tree[rt].ans=max(tree[rt<<1].r+tree[rt<<1|1].l,tree[rt].ans); if(tree[rt<<1].l==tree[rt<<1].len) tree[rt].l+=tree[rt<<1|1].l; if(tree[rt<<1|1].r==tree[rt<<1|1].len) tree[rt].r+=tree[rt<<1].r; } }
#include<cmath> #include<cstdio> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> #include<queue> #include<vector> #include<deque> using namespace std; typedef long long ll; //【p2253】好一个一中腰鼓(初中的回忆啊qwq) //一个01串,每次要求对某个点进行异或修改, //求每次修改后的最长连续(0和1交错出现)序列长度。 //l代表从线段树管理节点维护的左端点开始,向右的最大01序列的长度 //r代表从线段树管理节点维护的右端点开始,向左的最大01序列的长度 //ans代表整个区间最大的01序列(可以不含左,右端点) //lk代表区间左端点的颜色,rk代表区间右端点的颜色 struct node{ int l,r,lk,rk,ans,len; }tree[1500019]; void PushUp(int rt){ //向上求出管理节点 tree[rt].l=tree[rt<<1].l,tree[rt].r=tree[rt<<1|1].r; tree[rt].lk=tree[rt<<1].lk,tree[rt].rk=tree[rt<<1|1].rk; tree[rt].ans=max(tree[rt<<1].r,tree[rt<<1].ans); tree[rt].ans=max(tree[rt<<1|1].l,tree[rt].ans); tree[rt].ans=max(tree[rt].ans,tree[rt<<1|1].ans); //两边的两方向的ans值 if(tree[rt<<1].rk!=tree[rt<<1|1].lk){ //中间两节点可以合并,序列合并 tree[rt].ans=max(tree[rt<<1].r+tree[rt<<1|1].l,tree[rt].ans); if(tree[rt<<1].l==tree[rt<<1].len) //左儿子节点的维护的整个区间都是可行序列 tree[rt].l+=tree[rt<<1|1].l; //从rt维护的左端点开始的最长01序列可以合并右边 if(tree[rt<<1|1].r==tree[rt<<1|1].len) //右儿子节点的维护的整个区间都是可行序列 tree[rt].r+=tree[rt<<1].r; //从rt维护的右端点开始的最长01序列可以合并左边 } } void build(int l,int r,int rt){ //建立线段树 tree[rt].len=r-l+1; if(l==r){ //叶子节点 tree[rt].l=tree[rt].r=tree[rt].ans=1; tree[rt].lk=tree[rt].rk=0; return; } //↑↑相当于把a数组的值赋到线段树的求和数组中 int mid=(l+r)>>1; build(l,mid,rt<<1); //左儿子编号2*rt build(mid+1,r,rt<<1|1); //右儿子编号2*rt+1 PushUp(rt); } void update(int p,int l,int r,int rt){ //单点修改 if(l==r){ tree[rt].lk=tree[rt].rk=tree[rt].lk^1; return; } int mid=(l+r)>>1; //↓↓此时p在区间左半边 if(p<=mid) update(p,l,mid,rt<<1); //递归左儿子寻找p else update(p,mid+1,r,rt<<1|1); //递归右儿子寻找p PushUp(rt); //修改这条线路上的sum值 } int main(){ int n,m; scanf("%d%d",&n,&m); build(1,n,1); //建树并求和 for(int i=1;i<=m;i++){ scanf("%d",&x),update(x,1,n,1); printf("%d\n",tree[1].ans); } //在根节点统计全局的01序列最大长度 }
T16:【CF438D】
- 给定数列,区间查询和,区间取模,单点修改。
利用 ‘ 区间max<模数时,整个区间都不用取模 ’ 性质优化暴力时间。
#include<cmath> #include<cstdio> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> #include<queue> #include<vector> #include<deque> using namespace std; typedef long long ll; //【CF438D】给定数列,区间查询和,区间取模,单点修改。 // 利用‘区间max<模数时,整个区间都不用取模’性质优化暴力时间。 #define N 100019 #define ls rt<<1 #define rs rt<<1|1 int n,m,maxx[N<<2]; ll sum[N<<2]; inline int reads(){ char c=getchar();int num=0,f=1; for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) num=num*10+c-'0'; return num*f; } inline void pushup(int rt) { sum[rt]=sum[ls]+sum[rs],maxx[rt]=max(maxx[ls],maxx[rs]); } void build(int rt, int l, int r){ if(l==r){ maxx[rt]=sum[rt]=reads(); return; } int mid=(l+r)>>1; build(ls,l,mid),build(rs,mid+1,r),pushup(rt); } void add(int rt, int l, int r, int p, int val) { if(l==r){ maxx[rt]=sum[rt]=val; return; } int mid=(l+r)>>1; if(p<=mid) add(ls,l,mid,p,val); else add(rs,mid+1,r,p,val); pushup(rt); } ll csum(int rt, int l, int r, int L, int R) { if(L<=l&&r<=R) return sum[rt]; int mid=(l+r)>>1; ll ans=0; if(L<=mid) ans+=csum(ls,l,mid,L,R); if(R>mid) ans+=csum(rs,mid+1,r,L,R); return ans; } //区间求和 void modify(int rt,int l,int r,int L,int R,int p) { if(maxx[rt]<p) return; //区间max<模数,整个区间都不用取模 if(l==r){ sum[rt]%=p;maxx[rt]%=p; return; } int mid=(l+r)>>1; //↑↑找到区间的所有叶子节点,暴力更新 if(L<=mid) modify(ls,l,mid,L,R,p); if(R>mid) modify(rs,mid+1,r,L,R,p); pushup(rt); } int main(){ n=reads(),m=reads(); build(1,1,n); for(int i=1,k,x,y,z;i<=m;i++){ k=reads(),x=reads(),y=reads(); if(k==1) printf("%lld\n",csum(1,1,n,x,y)); if(k==2) z=reads(),modify(1,1,n,x,y,z); if(k==3) add(1,1,n,x,y); } }
T17:【p2073】送花
- 1 X C 添加一朵美丽值为x,价格为c的花。
- (如果加入花朵的价格与已有花朵 ‘ 重复 ’ 则不能加入。)
- 2 删除最便宜的一朵花。3 删除最贵的一朵花。
- (若删除操作时没有花,则跳过删除操作。)
- -1 开始包装花束,输出所有花的美丽值的总和 和 总价格。
以价格c为下标建立线段树。 原来这个就叫“权值线段树”???是我孤陋寡闻...
对于每个新的花,在线段树的c处添加美丽值为x;删除操作就是清零。
#include<cmath> #include<cstdio> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> #include<queue> #include<vector> #include<deque> using namespace std; typedef long long ll; /*【p2073】送花 1 X C 添加一朵美丽值为x,价格为c的花。 (如果加入花朵的价格与已有花朵【重复】则不能加入。) 2 删除最便宜的一朵花。3 删除最贵的一朵花。 (若删除操作时没有花,则跳过删除操作。) -1 开始包装花束,输出所有花的美丽值的总和 和 总价格。*/ int n=1000019,op,xi,ci; struct node{ int x,c; }tree[5000019]; void PushUp(int rt){ //向上求出管理节点 tree[rt].x=tree[rt<<1].x+tree[rt<<1|1].x; tree[rt].c=tree[rt<<1].c+tree[rt<<1|1].c; } //维护 区间美观值的和 和 区间价格的和 void add(int p,int x,int c,int l,int r,int rt){ if(l==r){ if(tree[rt].c) return; //价格重复不能加入 tree[rt].c=c,tree[rt].x=x; return; } int mid=(l+r)>>1; //↓↓寻找p(即c值) if(p<=mid) add(p,x,c,l,mid,rt<<1); else add(p,x,c,mid+1,r,rt<<1|1); PushUp(rt); //修改这条线路上的sum值 } void del(int l,int r,int rt,int flag){ //按下标的单调性确定min/max值 //↑↑因为是按照价格从小到大作为下标存的,所以只需要寻找该位置有没有值 if(l==r){ tree[rt].x=tree[rt].c=0; return; } int mid=(l+r)>>1; //选择性递归左右子树 if(flag&&tree[rt<<1].c&&tree[rt<<1|1].c) del(mid+1,r,rt<<1|1,flag); else if(!tree[rt<<1].c) del(mid+1,r,rt<<1|1,flag); else del(l,mid,rt<<1,flag); //求max/min值 PushUp(rt); //状态上移 } int main(/*hs_love_wjy*/){ while(scanf("%d",&op)&&op!=-1){ if(op==1) scanf("%d%d",&xi,&ci),add(ci,xi,ci,1,n,1); if(op==2) del(1,n,1,1); if(op==3) del(1,n,1,0); } cout<<tree[1].x<<" "<<tree[1].c<<endl; }
T18:【p4513】小白逛公园
- 维护一个动态的带修改最大子段和。
此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。
即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和,
rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【p4513】小白逛公园 维护一个动态的带修改最大子段和。*/ //此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。 //即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和, //rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。 void reads(int &x){ //读入优化(正负整数) int f=1;x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; //正负号 } struct Node{ int l,r,lm,rm,ans,tot; }tree[4000005]; //记录总和tot:用于更新包含区间左右的区间最大子段和(lm、rm) int n,m,op,x,y,cnt=0,a[500001]; void PushUp(int rt){ //合并答案,向上传递 int ll=rt<<1,rr=rt<<1|1; tree[rt].tot=tree[ll].tot+tree[rr].tot; tree[rt].lm=max(tree[ll].lm,tree[ll].tot+tree[rr].lm); tree[rt].rm=max(tree[rr].rm,tree[rr].tot+tree[ll].rm); tree[rt].ans=max(max(tree[ll].ans,tree[rr].ans),tree[ll].rm+tree[rr].lm); } void build(int l,int r,int rt){ tree[rt].l=l,tree[rt].r=r; if(l==r){ tree[rt].lm=tree[rt].rm=tree[rt].tot=a[l]; tree[rt].ans=a[l]; return; } int mid=(l+r)>>1; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1); PushUp(rt); //合并答案,向上传递 } void update(int rt,int p,int num){ int x=tree[rt].l,y=tree[rt].r,mid=(x+y)>>1; if(x==y){ tree[rt].lm=tree[rt].rm=tree[rt].tot=num; tree[rt].ans=num; return; //到达相应叶子节点,进行修改 } if(p<=mid) update(rt<<1,p,num); else update(rt<<1|1,p,num); PushUp(rt); } Node query(int rt,int l,int r){ int x=tree[rt].l,y=tree[rt].r; if(l<=x&&r>=y) return tree[rt]; //完全包含rt管理的区间 int mid=(x+y)>>1,ll=rt<<1,rr=rt<<1|1; if(r<=mid) return query(ll,l,r); else if(l>mid) return query(rr,l,r); else{ //询问区间跨过mid时要合并答案 Node t,t1=query(ll,l,r),t2=query(rr,l,r); t.lm=max(t1.lm,t1.tot+t2.lm); t.rm=max(t2.rm,t2.tot+t1.rm); t.ans=max(max(t1.ans,t2.ans),t1.rm+t2.lm); return t; //返回Node编号 } } int main(){ reads(n),reads(m); for(int i=1;i<=n;i++) reads(a[i]); build(1,n,1); //初始建树 for(int i=1;i<=m;i++){ reads(op),reads(x),reads(y); if(op==1){ if(x>y) swap(x,y); printf("%d\n",query(1,x,y).ans); } else update(1,x,y); } }
T19:【SP1043】GSS1
- 求区间的最大子段和。和上一题完全一样。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> using namespace std; typedef long long ll; typedef unsigned long long ull; #define R register /*【SP1043】GSS1 // 最大子段和 */ //此最大子段和可以通过分类讨论,将两个区间合并为一,从而用线段树维护。 //即:用tot表示区间数总和,lm表示区间左端点开始的最大子段和, //rm表示区间右端点开始的最大子段和,ans表示整个区间的最大子段和。 void reads(int &x){ //读入优化(正负整数) int f=1;x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x*=f; //正负号 } struct Node{ int l,r,lm,rm,ans,tot; }tree[4000005]; //记录总和tot:用于更新包含区间左右的区间最大子段和(lm、rm) int n,m,op,x,y,cnt=0,a[500001]; void PushUp(int rt){ //合并答案,向上传递 int ll=rt<<1,rr=rt<<1|1; tree[rt].tot=tree[ll].tot+tree[rr].tot; tree[rt].lm=max(tree[ll].lm,tree[ll].tot+tree[rr].lm); tree[rt].rm=max(tree[rr].rm,tree[rr].tot+tree[ll].rm); tree[rt].ans=max(max(tree[ll].ans,tree[rr].ans),tree[ll].rm+tree[rr].lm); } void build(int l,int r,int rt){ tree[rt].l=l,tree[rt].r=r; if(l==r){ tree[rt].lm=tree[rt].rm=tree[rt].tot=a[l]; tree[rt].ans=a[l]; return; } int mid=(l+r)>>1; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1); PushUp(rt); //合并答案,向上传递 } void update(int rt,int p,int num){ int x=tree[rt].l,y=tree[rt].r,mid=(x+y)>>1; if(x==y){ tree[rt].lm=tree[rt].rm=tree[rt].tot=num; tree[rt].ans=num; return; //到达相应叶子节点,进行修改 } if(p<=mid) update(rt<<1,p,num); else update(rt<<1|1,p,num); PushUp(rt); } Node query(int rt,int l,int r){ int x=tree[rt].l,y=tree[rt].r; if(l<=x&&r>=y) return tree[rt]; //完全包含rt管理的区间 int mid=(x+y)>>1,ll=rt<<1,rr=rt<<1|1; if(r<=mid) return query(ll,l,r); else if(l>mid) return query(rr,l,r); else{ //询问区间跨过mid时要合并答案 Node t,t1=query(ll,l,r),t2=query(rr,l,r); t.lm=max(t1.lm,t1.tot+t2.lm); t.rm=max(t2.rm,t2.tot+t1.rm); t.ans=max(max(t1.ans,t2.ans),t1.rm+t2.lm); return t; //返回Node编号 } } int main(){ reads(n); for(int i=1;i<=n;i++) reads(a[i]); reads(m); build(1,n,1); //初始建树 for(int i=1;i<=m;i++) reads(x),reads(y), printf("%d\n",query(1,x,y).ans); }
T20:
——时间划过风的轨迹,那个少年,还在等你