线段树/树状数组练习
NC14522 珂朵莉的数列(int128+树状数组)
题意:
珂朵莉有一个序列,有$n * ( n - 1 ) / 2$个子区间,求出每个子区间逆序对的对数
思路:
如果是只求整个序列的逆序对个数的话,直接从后向前树状数组动态加点统计当前有多少个小于当前点的数就好了
但是要算所有区间的对数的话,暴力肯定会超时,所以我们可以计算每个逆序对对答案的贡献,这就是这个逆序对所在的区间的个数
这个区间的个数很明显就为 $ i * \sum (n-j+1) $,其中j为i右边比它小的位置
然后树状数组下标存的就是$( n - j + 1 )$,由于每个数出现的次数不一定为1,所以有的下标对应的值要累加,并且由于$ a _{i} $很大,要先进行离散化
还有比较坑的就是。。。最后居然连$ unsigned $ $ long $ $ long $都爆了,要用$int128$
#include<iostream> #include<algorithm> #define lowbit(x) x&(-x) using namespace std; typedef unsigned long long ll; const int maxn=1e6+10; ll a[maxn],c[maxn],n,b[maxn]; inline void print(__int128 x){ if(x<0){ putchar('-'); x=-x; } if(x>9) print(x/10); putchar(x%10+'0'); } void add(ll x,ll val) { while(x<=n){ c[x]+=val; x+=lowbit(x); } } ll sum(ll x) { ll ans=0; while(x>=1){ ans+=c[x]; x-=lowbit(x); } return ans; } int main() { scanf("%lld",&n); __int128 ans=0; for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); b[i]=a[i]; } sort(b+1,b+1+n); int len=unique(b+1,b+1+n)-b-1; for(int i=n;i>=1;i--){ int pos=lower_bound(b+1,b+1+len,a[i])-b; ans+=(__int128)i*sum(pos-1); add(pos,n-i+1); } print(ans); return 0; }
NC 14136 监视任务(贪心+线段树)
题意:
给一些区间并且每个点最多为$1$,要求这些区间的和最少要为$x$,求整个和区间的最小值
思路:
先将区间按照右端点排序,之后遍历每个区间,如果区间和小于规定值,那就从区间的右端点向左依次加$1$(如果该点未被访问),直到符合要求
#include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=5e5+10; int ans[maxn<<2],vis[maxn]; struct node{ int l,r,v; }a[maxn<<1]; int cmp(node a,node b){ if(a.r==b.r) return a.l<b.l; return a.r<b.r; } void PushUp(int rt){ ans[rt]=ans[rt<<1]+ans[rt<<1|1]; } void ADD(int L,int C,int l,int r,int rt) { if (l==r){ ans[rt]+=C; return; } int mid=(l+r)>>1; if (L<=mid) ADD(L,C,l,mid,rt<<1); else ADD(L,C,mid+1,r,rt<<1|1); PushUp(rt); } int Query(int L,int R,int l,int r,int rt) { if (L<=l&&r<=R) return ans[rt]; int mid=(l+r)>>1; int ANS=0; if (L<=mid) ANS+=Query(L,R,l,mid,rt<<1); if (R>mid) ANS+=Query(L,R,mid+1,r,rt<<1|1); return ANS; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].v); sort(a+1,a+1+m,cmp); for(int i=1;i<=m;i++){ int l=a[i].l,r=a[i].r,val=a[i].v; int sum=Query(a[i].l,a[i].r,1,n,1); if(sum>=val) continue; for(int j=r;j>=l;j--){ if(!vis[j]){ vis[j]=1; ADD(j,1,1,n,1); sum++; } if(sum==val) break; } } cout<<Query(1,n,1,n,1); return 0; } /* 11 5 3 7 3 8 10 3 6 8 1 1 3 1 10 11 1 */
NC 14269 Sum(树状数组)
题意:
给一个序列,有两种操作,$1$单点修改,$2$统计区间所有子集的$and$运算和
思路:
由于是位运算,每个位置上的贡献均独立所以我们对每一位分别考虑
由于$&$运算的性质,要所有数都为$1$,结果才为$1$,所以对于拆位之后的集合大小为$x$的集合,符合要求的集合个数就为$C_{x}^{k}$,其中k是所有数中当前位上为1的个数
所以在二进制中第$j$为的贡献就为$2^{j} * (C_{k}^{1}+C_{k}^{2}+....+C_{k}^{k}) = 2^{j} * (2^{k}-1) $
对于修改比较简单,只需要考虑修改之后每一位上$1$个数的变化即可
#include<iostream> #include<algorithm> #define lowbit(x) x&(-x) using namespace std; typedef long long LL; const int maxn=1e5+10; const int mod=1e9+7; int a[maxn],c[30][maxn],p[maxn],n; void add(int x,int num,int val) { while(x<=n){ c[num][x]+=val; x+=lowbit(x); } } LL sum(int x,int num) { LL ans=0; while(x>=1){ ans=(ans+c[num][x])%mod; x-=lowbit(x); } return ans; } int main() { int m,op,x,y; scanf("%d",&n); p[0]=1; for(int i=1;i<=n;i++) p[i]=p[i-1]*2%mod; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); for(int j=0;j<30;j++) if((a[i]>>j)&1) add(i,j,1); } scanf("%d",&m); for(int i=1;i<=m;i++){ scanf("%d%d%d",&op,&x,&y); if(op==1){ for(int j=0;j<30;j++){ int k1=(a[x]>>j)&1; int k2=(y>>j)&1; if(k1!=k2){ if(k1==1) add(x,j,-1); else if(k1==0) add(x,j,1); } } a[x]=y; } else{ LL ans=0; for(int j=0;j<30;j++){ int num=sum(y,j)-sum(x-1,j); ans=(ans+(1LL<<j)*(p[num]-1)%mod)%mod; } printf("%lld\n",ans); } } return 0; }
P4145 上帝造题的七分钟2 / 花神游历各国
题意:
给一个数组,进行两组操作:$1$.区间里的每个数开平方 $2$.区间求和
思路:
观察一下数据范围,发现$ a_{i} ≤ 10^{12}$ ,对于这个数据范围内的数只要进行$ 6 $次开方操作就会变成1
所有,对于每次修改,只要区间内的最大值不为$ 1 $的话我们就暴力对区间内每个不为$1$的数进行开平方操作,这样的最坏时间复杂度为$ O(6*N) $
#include<iostream> #include<algorithm> #include<cmath> #include<cstdio> #define lson rt<<1 #define rson rt<<1|1 using namespace std; typedef long long ll; const int maxn=1e5+10; struct node{ ll sum,mx; }t[maxn<<2]; ll a[maxn]; void push_up(int rt) { t[rt].mx=max(t[lson].mx,t[rson].mx); t[rt].sum=t[lson].sum+t[rson].sum; } void build(int l,int r,int rt) { if(l==r){ t[rt].mx=t[rt].sum=a[l]; return; } int mid=(l+r)>>1; build(l,mid,lson); build(mid+1,r,rson); push_up(rt); } void update(int l,int r,int L,int R,int rt) { if(t[rt].mx==1) return; if(l==r&&l>=L&&r<=R){ a[l]=t[rt].mx=t[rt].sum=sqrt(a[l]); return; } int mid=(l+r)>>1; if(L<=mid) update(l,mid,L,R,lson); if(R>mid) update(mid+1,r,L,R,rson); push_up(rt); } ll query(int l,int r,int L,int R,int rt) { if(L<=l&&r<=R){ return t[rt].sum; } ll ans=0; int mid=(l+r)>>1; if(L<=mid) ans+=query(l,mid,L,R,lson); if(R>mid) ans+=query(mid+1,r,L,R,rson); return ans; } int main() { int n,m,op,l,r; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,n,1); scanf("%d",&m); for(int i=1;i<=m;i++){ scanf("%d%d%d",&op,&l,&r); if(l>r) swap(l,r); if(op==0) update(1,n,l,r,1); else cout<<query(1,n,l,r,1)<<endl; } return 0; }
P4513 小白逛公园
题意:
求带单点修改的最大区间最大子段和
思路:
单点修改还是比较简单的,但是问题就在于怎么合并区间,为了得到答案,我们选择维护区间的和,区间的左右连续最大值与区间连续最大值
再进行区间合并时,我们来看看我们维护的值应该怎么合并,左最大值可能是原来左边区间的左最大值与整个做区间的和加上右区间中左最大值,右最大值同理
那么整个区间的最大值,就会合并后的等于左最大值或者右最大值,还有可能是左区间的右最大值加上右区间的左最大值(因为区间要连续,不能进行简单的最大值累加)
第二个比较难的地方就是区间查询了,由于查询的区间会在线段树上分成许多小部分,所有在查询时我们也要进行区间合并,最后返回
#include<iostream> #include<algorithm> #include<cstdio> #define ls rt<<1 #define rs rt<<1|1 using namespace std; typedef long long ll; const int maxn=5e5+10; struct node{ ll sum,ans,lsum,rsum; }t[maxn<<2]; ll a[maxn]; void push_up(int rt) { t[rt].sum=t[ls].sum+t[rs].sum; t[rt].lsum=max(t[ls].lsum,t[ls].sum+t[rs].lsum); t[rt].rsum=max(t[rs].rsum,t[rs].sum+t[ls].rsum); t[rt].ans=max(max(t[ls].ans,t[rs].ans),t[ls].rsum+t[rs].lsum); } void build(int l,int r,int rt) { if(l==r){ t[rt].lsum=t[rt].rsum=t[rt].sum=t[rt].ans=a[l]; return; } int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); push_up(rt); } void update(int l,int r,int L,int C,int rt) { if(l==r&&l==L){ t[rt].lsum=t[rt].rsum=t[rt].sum=t[rt].ans=a[l]=C; return; } int mid=(l+r)>>1; if(L<=mid) update(l,mid,L,C,ls); if(L>mid) update(mid+1,r,L,C,rs); push_up(rt); } node query(int l,int r,int L,int R,int rt) { if(l>=L&&r<=R) return t[rt]; int mid=(l+r)>>1; if(R<=mid) return query(l,mid,L,R,ls); else if(L>mid) return query(mid+1,r,L,R,rs); else{ node now,l1=query(l,mid,L,R,ls),r1=query(mid+1,r,L,R,rs); now.sum=l1.sum+r1.sum; now.lsum=max(l1.lsum,l1.sum+r1.lsum); now.rsum=max(r1.rsum,r1.sum+l1.rsum); now.ans=max(max(l1.ans,r1.ans),l1.rsum+r1.lsum); return now; } } int main() { int n,m,op,l,r; node now; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,n,1); for(int i=1;i<=m;i++){ scanf("%d%d%d",&op,&l,&r); if(op==2) update(1,n,l,r,1); else{ if(l>r) swap(l,r); now=query(1,n,l,r,1); printf("%lld\n",now.ans); } } return 0; }