怕期末月完了就忘完了,蒟蒻总结一波。
先来模板题:求静态区间第k小
https://www.luogu.com.cn/problem/P3834
对于板子的理解:
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
LL a[maxn],b[maxn],root[maxn];
LL tot=0;
struct Tree{
LL l,r,rt,sum;
}tree[maxn*20];
LL update(LL pre,LL l,LL r,LL x){
LL rt=++tot;
tree[rt].l=tree[pre].l;
tree[rt].r=tree[pre].r;
tree[rt].sum=tree[pre].sum+1;
LL mid=(l+r)>>1;
if(l<r){
if(x<=mid){
tree[rt].l=update(tree[pre].l,l,mid,x);
}
else tree[rt].r=update(tree[pre].r,mid+1,r,x);
}
return rt;
}
LL query(LL v,LL u,LL l,LL r,LL k){
if(l==r) return l;
LL x=tree[tree[u].l].sum-tree[tree[v].l].sum;
LL mid=(l+r)>>1;
if(x>=k){
return query(tree[v].l,tree[u].l,l,mid,k);
}
else return query(tree[v].r,tree[u].r,mid+1,r,k-x);
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;cin>>n>>m;
for(LL i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
LL siz=unique(b+1,b+1+n)-b-1;///返回不同的数字的个数
for(LL i=1;i<=n;i++){
LL x=lower_bound(b+1,b+1+siz,a[i])-b;
root[i]=update(root[i-1],1,siz,x);
}
while(m--){
LL l,r,k;cin>>l>>r>>k;
LL t=query(root[l-1],root[r],1,siz,k);
cout<<b[t]<<endl;
}
return 0;
}
以下是静态区间题的应用:
一.求树上的某条路径上第k小的点(LCA+主席树)
Count on a tree
二.求树中两点路径的不同颜色数量(树上莫队)
Count on a tree II
SPOJ - COT2
详解:这里
三.区间内小于等于k的数字有多少)[主席树+二分]
四.主席树维护线段树区间修改(标记永久化)
To the moon
思路:
我们就一直把懒标记留在1和5两个点上,不用下传,查询的时候再加。
比如我们要查询6节点的值。
先从1节点开始,向右边走,顺便加上它的懒标记33。
到了5节点,又往左走,也要加上懒标记55。
最后到了6节点,得到它的现在值就是它的原值加上刚刚加上的懒标记的和。
区间查询的话,懒标记加上时要乘上区间元素个数。(见懒标记定义)
个人理解:其实过程是这样的,为了防止主席树的lazy标记下传影响共用节点,于是有了标记永久化。
也就是对于一个区间,我们只维护lazy而不下传,但是注意我们维护push_up;
在update中,对要进行修改的区间进行打tag,打完先不更新这个点的和,打完后push_up(当前父亲的区间和不仅由左右儿子相加,还有左右儿子的tag*左右儿子对应的区间)。
对于下一棵树的建树,在前一棵上先复制。到了这一颗树要打tag的时候,我们将这个点tag+=这次打的标记(因为之前的我们复制了)打完了之后然后同理push_up上去。
对于查询,我们在查询的过程中注意累加当前树区间的tag对于结果区间的影响,也就是LL cnt=tree[now].add*(QR-QL+1);
对于这种查询,用三种区间的查询容易操作,分别是全在左子树,全在右子树,和跨越区间。注意跨越区间的时候要将查询的区间修改了(因为我们的QR-QL+1的大小是要变化的,不然每次都乘一个最开始的大区间了)
找到了对应的完全覆盖区间注意别忘了加上这个区间的add*(这个区间的长度)。然后累加
代码比较好理解
注意如果是hdu题的话,这题要多组,同时对sum有关的都开long long.洛谷不用多组。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+50;
typedef int LL;
inline LL read()
{ LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL root[maxn],tot=0;
struct Tree{
LL lson,rson,add;long long sum;
/// LL l,r;///节点代表的左右区间
}tree[maxn<<5];
LL a[maxn];
LL build(LL l,LL r){
LL rt=++tot;
///tree[rt].l=l;tree[rt].r=r;
if(l==r) {
tree[rt].sum=a[l];
return rt;
}
LL mid=(l+r)>>1;
tree[rt].lson=build(l,mid);
tree[rt].rson=build(mid+1,r);
tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum;
return rt;
}
LL update(LL pre,LL l,LL r,LL ql,LL qr,long long val){
LL rt=++tot;
tree[rt]=tree[pre];
if(ql<=l&&qr>=r){
tree[rt].add+=val;///打lazy标记
///tree[rt].sum+=val*(tree[rt].r-tree[rt].l+1);不能先加
return rt;
}
LL mid=(l+r)>>1;
if(ql<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,ql,qr,val);
}
if(qr>mid){
tree[rt].rson=update(tree[pre].rson,mid+1,r,ql,qr,val);
}
LL add1=tree[tree[rt].lson].add;
LL add2=tree[tree[rt].rson].add;
tree[rt].sum=tree[tree[rt].lson].sum+tree[tree[rt].rson].sum+add1*(mid-l+1)+add2*(r-(mid+1)+1);
return rt;
}
long long query(LL now,LL l,LL r,LL ql,LL qr){
if(ql<=l&&qr>=r){
return tree[now].sum+tree[now].add*(r-l+1);
}
long long cnt=tree[now].add*(qr-ql+1);
LL mid=(l+r)>>1;
if(qr<=mid) return cnt+query(tree[now].lson,l,mid,ql,qr);
else if(ql>mid) return cnt+query(tree[now].rson,mid+1,r,ql,qr);
else{
return cnt+query(tree[now].lson,l,mid,ql,mid)+query(tree[now].rson,mid+1,r,mid+1,qr);///注意由于标记永久化,这里往下搜的ql,qr要变(因为lazy乘要乘长度)
}
}
int main(void)
{
char op[10];
LL n,m;
while(~scanf("%d%d",&n,&m)){
LL TIME=0;
LL tot=0;
for(LL i=1;i<=n;i++){
a[i]=read();
}
root[0]=build(1,n);
for(LL i=1;i<=m;i++){
scanf("%s",op);
if(op[0]=='C'){LL l,r,d;l=read();r=read();d=read();TIME++;root[TIME]=update(root[TIME-1],1,n,l,r,d); }
if(op[0]=='Q'){LL l,r;l=read();r=read(); long long ans=query(root[TIME],1,n,l,r);printf("%lld\n",ans); }
if(op[0]=='H'){LL l,r,t;l=read();r=read();t=read();long long ans=query(root[t],1,n,l,r);printf("%lld\n",ans); }
if(op[0]=='B'){LL t;t=read();TIME=t;}
}
}
return 0;
}
五.求区间内不同数字的个数
这个问题其实可以莫队解决,但是如果卡nsqrt(n)呢?还是有必要掌握的。
P1972 [SDOI2009]HH的项链(这题我看最近有人莫队卡过去了,反正我没卡过去。好像主席树这题也能卡)
D-query
思路:如果权值比较大要进行离散化。
扫描序列建立可持续化线段树。
对每个pos建立主席树。pos对应单点修改的位置,找到+=val;
如果未出现,就赋值当前位置去找。不然先在root[i-1]基础上减去上次这个数字的贡献,再加这次这个数字的贡献。
同时维护每一个元素上一次出现的位置。
query的时候:每一个主席树维护的是1~i(当前位置)的不同数字有多少个。
找到时候在root【r】中找,树中区间编号>=l的,就是1-r中,l~r的贡献所求。
结合代码来模拟就是说:
当前这棵权值线段树维护的是1-pos(循环到的r)中,有多少不同的数字。
比如序列 1 2 3 4,那么对于最后一棵权值线段树来说,里面有4个1,代表1-4中有4个不同的数字。如果要找2-4中有几个,那么对最后一棵树进行区间和的查找,找出来是2,对应答案就是2.
比如序列1 2 2 2 3 4 .对于第3棵权值线段树来说,我先在第2棵的基础上减去了这个pos=2的影响(-1),这时候临时权值线段树其叶子节点2的值0.然后我再在第三棵上加上pos=3,这时候第三颗的叶子节点3的权值是1。这时候第三棵权值线段树的1的总和是2,也就是代表1-3中不同的数字总共有2个。如果在第三棵树中找【2,3】有多少不同的数字,那么l>=2的情况下其区间和是1.
比较巧妙
代码:这里
六.求区间mex
思路:十分巧妙。首先数值过大,我们将其离散化,对一个序列,其可能出现的答案是a[n+1],0,所以我们在离散化的时候要把a[i]+1放入离散化的b[]数组。[因为有可能答案出现在里面]
对于离散化去重之后的新siz,建立主席树。
对于离散化后的数字,按照出现的次序建立权值线段树。我们维护这个数字在区间内出现的最新位置。然后push_up维护每个节点的min位置。
那么对于第i棵权值线段树,我们在root[i[就是说,在1-i中,每个数字出现的最新位置。
比如1 2 3 4 1 .对于第4棵线段树和第5棵线段树。第4棵最早出现的数字的位置是pos=1,所以root[4]的左儿子的minval是1,如果这时候去[2,4]的话,由于左子树的节点小于l=2,往左子树一直找,最后返回1
然后对于第5棵线段树,1这个数字的叶子权值改成5,push上去root[5]的左儿子的minval不是1了,而是2,这时候去找[2,5],那么就会先去找右儿子,直到找到一个提前离散化处理好的代表没出现的数字5的叶子节点的,其权值为0.对应2 3 4 1 中没有出现的最小数字是0
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=4e5+1000;
typedef long long LL;
LL a[maxn],b[maxn];///离散化
LL cnt=0;
struct Tree{
LL lson,rson,val;
}tree[maxn<<5];///nlogn
LL root[maxn];
void push_up(LL p){
tree[p].val=min(tree[tree[p].lson].val,tree[tree[p].rson].val);
}
///k表示输入的数的大小,val表示这次进来的这个数的pos
LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++cnt;///先复制
tree[rt].val=tree[pre].val;
tree[rt].lson=tree[pre].lson;
tree[rt].rson=tree[pre].rson;
if(l==r) {///叶子节点返回
tree[rt].val=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid) tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
LL query(LL rt,LL l,LL r,LL x){
if(l==r) {
return b[l];
}
LL mid=(l+r)>>1;
if(x<=tree[tree[rt].lson].val){
return query(tree[rt].rson,mid+1,r,x);
}
else return query(tree[rt].lson,l,mid,x);
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
///freopen("P4137_2.in","r",stdin);
///freopen("myoutput2.out","w",stdout);
LL n,m;cin>>n>>m;
LL idx=0;
b[++idx]=0;
for(LL i=1;i<=n;i++) {
cin>>a[i];
b[++idx]=a[i];b[++idx]=a[i]+1;///注意离散化的方式
}
sort(b+1,b+1+idx);
LL siz=unique(b+1,b+1+idx)-b-1;
for(LL i=1;i<=n;i++){
LL k=lower_bound(b+1,b+1+siz,a[i])-b;
/// debug(k);
root[i]=update(root[i-1],1,siz,k,i);
}
while(m--){
LL l,r;cin>>l>>r;
LL ans=query(root[r],1,siz,l);
/// debug(ans);
cout<<ans<<endl;
}
return 0;
}
七.求区间内出现次数大于>=k次的最前数
他人Fading的讲解:https://www.luogu.com.cn/problem/solution/CF840D
首先你要做过一道题目,\texttt{ P3567 [POI2014]KUR-Couriers} P3567 [POI2014]KUR-Couriers
那道题K=2K=2,但是这道题K\in [2,5]K∈[2,5]。
怎么办呢?考虑那道题的做法,主席树维护[1,x][1,x]内值域区间有多少个数。
然后线段树上二分,首先如果左子树的和\text{sum}_{lson}\leq \frac 12\text{sum}sumlson≤21sum,那么\text{sum}_{rson}\geq \frac 12\text{sum}sumrson≥21sum。所以那个大往那个子树走即可。
这个题怎么办呢?我们采取暴力,如果\text{su}\text{m}_{lson}\geq \frac 1K\text{su}\text{m}sumlson≥K1sum,往左子树走。如果左子树没有答案(即仅仅是和\geq\frac 1K≥K1),那么往右子树走。
看似时间复杂度不对,实际上是O(nK\log_2 n)O(nKlog2n)的。
下面是复杂度分析:
一次如果\text{su}\text{m}_{lson}\geq x\frac 1K\text{su}\text{m}(x \in N^*)sumlson≥xK1sum(x∈N∗),那么\text{su}\text{m}_{rson}\leq (K-x)\frac{1}{K}\text{su}\text{m}sumrson≤(K−x)K1sum。左子树最多就有xx个答案,最多搜索x\log_2nxlog2n次,同理右边最多搜索(K-x)\log_2n(K−x)log2n次。
个人理解:其实就是先维护一下区间内数字出现次数有多少次,然后由于比如一个节点的次数和很大,但是并不代表里面的某个叶子节点出现次数>=k,因为可以平分嘛。所以我们就暴力去找。如果左儿子的次数和>=k,那么暴力去找,找到底发现有了,就结束找了,如果找不到,就看右儿子够不够,够就去找,不够就回上一层。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=5e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
LL a[maxn],root[maxn];
struct Tree{
LL lson,rson,sum;
}tree[maxn<<5];
void push_up(LL p){
tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++tot;
///tree[rt]=tree[pre];
tree[rt].sum=tree[pre].sum;
tree[rt].lson=tree[pre].lson;
tree[rt].rson=tree[pre].rson;
if(l==r){
tree[rt].sum+=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
}
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
LL sum=tree[now].sum-tree[pre].sum;
if(sum<=x) return -1;
if(l==r) return l;
LL mid=(l+r)>>1;
LL ans=-1;
if(tree[tree[now].lson].sum-tree[tree[pre].lson].sum>x){
ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
if(ans>0){
return ans;
}
}
if(tree[tree[now].rson].sum-tree[tree[pre].rson].sum>x){
ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
if(ans>0){
return ans;
}
}
return ans;
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;n=read();m=read();
for(LL i=1;i<=n;i++) a[i]=read();
for(LL i=1;i<=n;i++){
root[i]=update(root[i-1],1,n,a[i],1);
}
while(m--){
LL l,r;l=read();r=read();
LL k=(r-l+1)/2;
LL ans=query(root[r],root[l-1],1,n,k);
if(ans==-1){
puts("0");
}
else printf("%lld\n",ans);
}
return 0;
}
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=3e5+1000;
typedef long long LL;
inline LL read()
{
LL x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
LL tot=0;
struct Tree{
LL lson,rson,val;
}tree[maxn<<5];
LL a[maxn],root[maxn];
void push_up(LL p){
tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++tot;
tree[rt].val=tree[pre].val;
tree[rt].lson=tree[pre].lson;
tree[rt].rson=tree[pre].rson;
if(l==r){///叶子递归边界
tree[rt].val+=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
}
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
LL query(LL now,LL pre,LL l,LL r,LL x){
if(tree[now].val-tree[pre].val<=x) return -1;
if(l==r){
return l;
}
LL res=1e18;
LL mid=(l+r)>>1;
if(tree[tree[now].lson].val-tree[tree[pre].lson].val>x){
LL ans=query(tree[now].lson,tree[pre].lson,l,mid,x);
if(ans>0){
res=min(res,ans);
}
}
if(tree[tree[now].rson].val-tree[tree[pre].rson].val>x){
LL ans=query(tree[now].rson,tree[pre].rson,mid+1,r,x);
if(ans>0){
res=min(res,ans);
}
}
return res;
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;
n=read();m=read();
for(LL i=1;i<=n;i++){
a[i]=read();
}
for(LL i=1;i<=n;i++){
root[i]=update(root[i-1],1,n,a[i],1);
}
while(m--){
LL l,r,k;l=read();r=read();k=read();
LL want=(r-l+1)/k;
LL shu=query(root[r],root[l-1],1,n,want);
if(shu==1e18) puts("-1");
else printf("%lld\n",shu);
}
return 0;
}
八.二分点建主席树
Sign on Fence CodeForces - 484E
比较套路:
讲解来自:
寻找中位数有一个通常的套路:
考虑二分中位数,设x 是现在二分的数,mid是序列的中位数:
将大于x 的数设为1 ,小于x 的数设为−1。
将整个1/−1序列求和
若Sum>0,说明1的数量比−1多,也就是大于x的数比小于x的数多
说明x<=mid
反之x>mid
回到本题,[b+1,c−1]为必选区间,[a,b]选后缀,[c,d]选前缀。
要使中位数尽可能大,我们要使Sum尽量大。
因为[b+1,c−1]固定且[a,b]后[c,d]前缀互不影响,所以在[a,b]区间我们选择最大后缀,在[ c , d ]区间我们选择最大前缀。
至此,我们已经有具体思路了:
线段树维护区间最大前缀,最大后缀,区间和,每次二分,用线段树判断可不可行。
但是,若使用普通线段树,每次查询都要重置区间为1/−1,所以查询时间复杂度为O(nlogn)
不TLE才奇怪。
考虑使用可持久化线段树,把每次二分后的1/−1序列预处理下来,每次查询就是查一个历史版本
总复杂度为O(nlogn+qlog2n)可以AC。
个人理解:对于序列按照权值从小到大排序后,先建立一颗空树。说明右边的数都比他大,此时该线段树的区间和只有一个-1,由此序列建立。
对于答案进行二分是第几个位置,由于我们的线段树是具有区间和从小到大的单调性,所以如果当前树的和>=0.说明还可以往右找,这样子找的数字符合题目要求的满足条件的最大数。找到后把这个编号返回到数组中,输出对应的数字大小。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e4+1000;
typedef long long LL;
inline LL read()
{ LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
LL tot=0;
struct Tree{
LL lson,rson,pre,suf,sum;///前缀最大,后缀最大,区间和;
}tree[maxn<<6];
LL root[maxn];
struct P{
LL x,pos;
}a[maxn];
void push_up(LL p){
tree[p].sum=tree[tree[p].lson].sum+tree[tree[p].rson].sum;
tree[p].pre=max(tree[tree[p].lson].pre,tree[tree[p].lson].sum+tree[tree[p].rson].pre);
tree[p].suf=max(tree[tree[p].rson].suf,tree[tree[p].rson].sum+tree[tree[p].lson].suf);
}
LL build(LL l,LL r){
LL rt=++tot;
if(l==r){
tree[rt].pre=tree[rt].suf=tree[rt].sum=1;
return rt;
}
LL mid=(l+r)>>1;
tree[rt].lson=build(l,mid);
tree[rt].rson=build(mid+1,r);
push_up(rt);
return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++tot;
tree[rt]=tree[pre];
if(l==r){
tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
}
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
LL query_sum(LL rt,LL l,LL r,LL L,LL R){
if(L<=l&&R>=r){
return tree[rt].sum;
}
LL ans=0;
LL mid=(l+r)>>1;
///debug(mid);
if(L<=mid) ans+=query_sum(tree[rt].lson,l,mid,L,R);
if(R>mid) ans+=query_sum(tree[rt].rson,mid+1,r,L,R);
return ans;
}
LL query_premax(LL rt,LL l,LL r,LL L,LL R){
if(L<=l&&R>=r){
return tree[rt].pre;
}
LL mid=(l+r)>>1;
/// debug(mid);
if(R<=mid){
return query_premax(tree[rt].lson,l,mid,L,R);
}
else if(L>mid){
return query_premax(tree[rt].rson,mid+1,r,L,R);
}
else return max(query_premax(tree[rt].lson,l,mid,L,R),query_sum(tree[rt].lson,l,mid,L,R)+query_premax(tree[rt].rson,mid+1,r,L,R));
}
LL query_sufmax(LL rt,LL l,LL r,LL L,LL R){
if(L<=l&&R>=r){
return tree[rt].suf;
}
LL mid=(l+r)>>1;
///debug(l);debug(r);
if(R<=mid){
return query_sufmax(tree[rt].lson,l,mid,L,R);
}
else if(L>mid){
return query_sufmax(tree[rt].rson,mid+1,r,L,R);
}
else return max(query_sufmax(tree[rt].rson,mid+1,r,L,R),query_sum(tree[rt].rson,mid+1,r,L,R)+query_sufmax(tree[rt].lson,l,mid,L,R));
}
bool cmp(P A,P B){
return A.x<B.x;
}
LL n;
LL test[5];
bool check(LL x,LL a,LL b,LL c,LL d){
LL sum=0;
/// cout<<"a="<<a<<" "<<"b="<<b<<" "<<"c="<<c<<" "<<"d="<<d<<endl;
if(! ( (c-1)-(b+1)<=0 )) sum+=query_sum(root[x],1,n,b+1,c-1);
/// cout<<"fuck"<<endl;
sum+=query_sufmax(root[x],1,n,a,b);
sum+=query_premax(root[x],1,n,c,d);
if(sum>=0) return true;
else return false;
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
n=read();
///题目是从下标0开始的,所以最后的询问算区间的时候要+1
for(LL i=1;i<=n;i++){
a[i].x=read();
a[i].pos=i;
}
sort(a+1,a+1+n,cmp);
root[1]=build(1,n);
for(LL i=2;i<=n+1;i++){
root[i]=update(root[i-1],1,n,a[i-1].pos,-1);
}
LL Q;Q=read();
LL last=0;
while(Q--){
test[0]=read();test[1]=read();test[2]=read();test[3]=read();
for(LL i=0;i<4;i++) test[i]=(test[i]+last)%n;
sort(test,test+4);
for(LL i=0;i<4;i++){
test[i]++;
}
LL l=1;LL r=n;
while(l<r){
LL mid=(l+r+1)>>1;
if(check(mid,test[0],test[1],test[2],test[3])) l=mid;
else r=mid-1;
}
last=a[l].x;
printf("%lld\n",a[l].x);
}
return 0;
}
思路:这题很相似。但是如果也按照从小到大去建立,会卡边界。
我是后来模拟发现这个bug的。因为我从小到大建立的时候,是先预处理一颗全1树,然后把当前数字给赋值0,二分l=mid去找。
但是我们要找的区间长度和是要包括这个点的。但是我们已经赋0了。
所以开始处理全0树,把值赋1,二分反为r=mid去找。
query最长连续1的时候注意了,跨越区间的时候要处理仔细。一个比较好的处理方法是在跨区间完了的下一步处理前后缀的最值,通过该区间的最长前缀和要找区间的长度取一个最小然后合并。取完之后再和左右单独区间的最大来获得一个最大。
具体看代码容易理解
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
struct P{
LL x,pos;
}a[maxn],b[maxn];
struct Tree{
LL lson,rson,pre,suf,sum;///pre-前缀最长连续1长度,suf后缀最长连续1长度,sum该区间最长连续1的长度
LL l,r;///该节点代表的区间
}tree[maxn<<5];
LL tot=0;
LL root[maxn];
bool cmp(P A,P B){
return A.x>B.x;
}
LL rmax(LL A,LL B,LL C){
return max(A,max(B,C));
}
void push_up(LL rt){
tree[rt].pre=tree[tree[rt].lson].pre;
tree[rt].suf=tree[tree[rt].rson].suf;
tree[rt].sum=rmax(tree[tree[rt].lson].sum,tree[tree[rt].rson].sum,tree[tree[rt].lson].suf+tree[tree[rt].rson].pre);
if(tree[tree[rt].lson].pre==tree[tree[rt].lson].r-tree[tree[rt].lson].l+1) tree[rt].pre+=tree[tree[rt].rson].pre;
if(tree[tree[rt].rson].suf==tree[tree[rt].rson].r-tree[tree[rt].rson].l+1) tree[rt].suf+=tree[tree[rt].lson].suf;
}
LL build(LL l,LL r,LL val){
LL rt=++tot;
tree[rt].l=l;tree[rt].r=r;
if(l==r){
tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
return rt;
}
LL mid=(l+r)>>1;
tree[rt].lson=build(l,mid,val);
tree[rt].rson=build(mid+1,r,val);
push_up(rt);
return rt;
}
LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++tot;
tree[rt]=tree[pre];
if(l==r){
tree[rt].pre=tree[rt].suf=tree[rt].sum=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
}
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
LL query(LL now,LL l,LL r,LL L,LL R){
if(L<=l&&R>=r) return tree[now].sum;
LL mid=(l+r)>>1;
if(R<=mid){
return query(tree[now].lson,l,mid,L,R);
}
else if(L>mid){
return query(tree[now].rson,mid+1,r,L,R);
}
else {
LL ans1=query(tree[now].lson,l,mid,L,R);
LL ans2=query(tree[now].rson,mid+1,r,L,R);
LL res=max(ans1,ans2);
LL ll=min(tree[tree[now].lson].suf,mid-L+1);///细节
LL rr=min(tree[tree[now].rson].pre,R-mid);///细节
res=max(res,ll+rr);
return res;
}
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n;n=read();
for(LL i=1;i<=n;i++){
a[i].x=read();
a[i].pos=i;
}
sort(a+1,a+1+n,cmp);
root[0]=build(1,n,0);///开始建全0空树
for(LL i=1;i<=n;i++){
root[i]=update(root[i-1],1,n,a[i].pos,1);
}
LL Q;Q=read();
while(Q--){
LL l,r,k;l=read();r=read();k=read();
LL L=1;LL R=n;
while(L<R){
LL mid=(L+R)>>1;
LL t=query(root[mid],1,n,l,r);
if(t>=k) R=mid;
else L=mid+1;
}
printf("%lld\n",a[L].x);
}
return 0;
}
Weng_Weijie的讲解:
这题和[国家集训队]middle很像
考虑二分答案
问题变成判断是否存在一段大于等于mid且长度不少于k的子区间
将大于等于mid设为1,小于mid设为0,就是查询区间最长全1区间是否超过k
这个东西可以通过维护前后缀最长全1区间,就支持区间加法了
由于每次二分的mid都不同,于是可以用主席树维护每个mid时的线段树
一点变式题:
The Preliminary Contest for ICPC Asia Xuzhou 2019 I. query
题意:求区间[l,r]内满足min(a[i],a[j])=gcd(a[i],a[j])(l<=i<j<=r)的对数。
思路:实际上是求一个区间内一个数是另外一个数的倍数的有序对数,因为是一个排列,所以容易知道这样的对数不超过nlogn。
nlogn的证明是一个经典的数论trick。序列中是当前数的倍数的数字一定能整除。
那么就有 这玩意算出来是n+n/2+n/3+...n/n=n*(1+1/2+1/3+....1/n) 后面是个调和级数==logn。证明在我的一篇博客里转载过。搜下调和级数的复杂度证明
然后我们就可以预处理每个数字他的因数。
再建立主席树,每颗主席树保存在1-i中是因子对数的个数状态,然后直接在第r颗线段树上查找位置在询问区间里面的个数就好了。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef int LL;
LL a[maxn],p[maxn];
LL tot=0,root[maxn];
vector<LL>v[maxn];
struct Tree{
LL lson,rson,val;
}tree[maxn*20*10];
inline void push_up(LL p){
tree[p].val=tree[tree[p].lson].val+tree[tree[p].rson].val;
}
inline LL update(LL pre,LL l,LL r,LL k,LL val){
LL rt=++tot;///此题要对root[i]这棵树本身进行多次修改
tree[rt]=tree[pre];
if(l==r){
tree[rt].val+=val;
return rt;
}
LL mid=(l+r)>>1;
if(k<=mid){
tree[rt].lson=update(tree[pre].lson,l,mid,k,val);
}
else tree[rt].rson=update(tree[pre].rson,mid+1,r,k,val);
push_up(rt);
return rt;
}
inline LL query(LL now,LL l,LL r,LL L,LL R){
if(L<=l&&R>=r){
return tree[now].val;
}
LL ans=0;
LL mid=(l+r)>>1;
if(L<=mid) ans+=query(tree[now].lson,l,mid,L,R);
if(R>mid) ans+=query(tree[now].rson,mid+1,r,L,R);
return ans;
}
inline LL read(){LL x=0,f=1;char ch=getchar();while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}return x*f;}
int main(void)
{
LL n,m;n=read();m=read();
for(LL i=1;i<=n;i++){
a[i]=read();
p[a[i]]=i;
}
///nlogn
for(LL i=1;i<=n;i++){
for(LL j=i*2;j<=n;j+=i){
if(p[i]<p[j]) v[p[j]].push_back(p[i]);
if(p[i]>p[j]) v[p[i]].push_back(p[j]);
}
}
for(LL i=1;i<=n;i++){
root[i]=root[i-1];///先复制
for(auto u:v[i]){
root[i]=update(root[i],1,n,u,1);
}
}
while(m--){
LL l,r;l=read();r=read();
LL ans=query(root[r],1,n,l,r);
printf("%d\n",ans);
}
return 0;
}
动态区间第k大(模板)
树状数组套主席树。具体的过程可以看另外一篇博客。学完之后有了一点新的理解。
P2617 Dynamic Rankings(动态区间第k大——树状数组套主席树)详解
特别理解到的一点:
update的过程,在静态主席树查询的过程中,代码是这样的:每次可以新开一颗主席树,不过更新的是和前一棵有区别的logn长链,剩下的直接指回去就好了。
但是注意不是所有情况都是新开点的。这个题的情况就是在原有的节点上覆盖更新。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
struct Tree{
LL l,r,rt,sum;
}tree[maxn*4*35];
struct operation{bool b;LL l,r,k;LL pos,t;}q[maxn];
LL n,m,a[maxn],root[maxn],o[maxn*2],tot=0,len=0;
LL temp[2][30],cnt[10];
void modify(LL &now,LL l,LL r,LL x,LL val){
if(!now) now=++tot;
tree[now].sum+=val;
if(l==r) return;
LL mid=(l+r)>>1;
if(x<=mid) modify(tree[now].l,l,mid,x,val);
else modify(tree[now].r,mid+1,r,x,val);
}
void pre_modify(LL pos,LL val){
LL x=lower_bound(o+1,o+1+len,a[pos])-o;
for(LL i=pos;i<=n;i+=lowbit(i)){
modify(root[i],1,len,x,val);///处理出需要修改哪log棵主席树
}
}
LL query(LL l,LL r,LL k){
if(l==r) return l;
LL mid=(l+r)>>1;LL sum=0;
for(LL i=1;i<=cnt[1];i++) sum+=tree[tree[temp[1][i]].l].sum;
for(LL i=1;i<=cnt[0];i++) sum-=tree[tree[temp[0][i]].l].sum;
if(k<=sum){
for(LL i=1;i<=cnt[1];i++){
temp[1][i]=tree[temp[1][i]].l;
}
for(LL i=1;i<=cnt[0];i++){
temp[0][i]=tree[temp[0][i]].l;
}
return query(l,mid,k);
}
else{
for(LL i=1;i<=cnt[1];i++){
temp[1][i]=tree[temp[1][i]].r;
}
for(LL i=1;i<=cnt[0];i++){
temp[0][i]=tree[temp[0][i]].r;
}
return query(mid+1,r,k-sum);
}
}
LL pre_query(LL l,LL r,LL k){
memset(temp,0,sizeof(temp));
cnt[0]=cnt[1]=0;
for(LL i=r;i;i-=lowbit(i)) temp[1][++cnt[1]]=root[i];
for(LL i=l-1;i;i-=lowbit(i)) temp[0][++cnt[0]]=root[i];
return query(1,len,k);
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
cin>>n>>m;
for(LL i=1;i<=n;i++){
cin>>a[i];
o[++len]=a[i];
}
for(LL i=1;i<=m;i++){
char op;cin>>op;
if(op=='Q'){
q[i].b=1; cin>>q[i].l>>q[i].r>>q[i].k;
}
else{
q[i].b=0;
cin>>q[i].pos>>q[i].t;
o[++len]=q[i].t;
}
}
sort(o+1,o+len+1);
len=unique(o+1,o+len+1)-o-1;
for(LL i=1;i<=n;i++){
pre_modify(i,1);
}
for(LL i=1;i<=m;i++){
if(q[i].b==1){
cout<<o[pre_query(q[i].l,q[i].r,q[i].k)]<<endl;
}
else{
pre_modify(q[i].pos,-1);
a[q[i].pos]=q[i].t;
pre_modify(q[i].pos,1);
}
}
return 0;
}
小结一下:主席树的维护还是相当需要思维含量的。不过好像大体建立好后要不在root[r]中找,要不通过root[r]-root[l]中找。剩下的就是看积累和思维了把,还有一定的套路固定的题型。
(希望期末月人没事我好菜啊