文章目录
- 前言
- 可持久化线段树
- 代码
- 区间第k大
- 代码
- 练习
- 粟粟的书架
- 代码
- 森林
- 代码
- 任务查询系统
- 代码
- 列队
- 代码
前言
主席树,全称是可持久化权值线段树
利用r和l-1两棵权值线段树作差得到[l,r]的信息
从而解决各种问题
在排名这方面功能极其强大
可持久化线段树
学主席树之前,我们当然要先掌握这个东西啦
其实也很简单,就是只增加每个版本信息相对于上一个版本不同的结点,其他结点保留
这样每次修改只会增加log个结点
保证空间复杂度的正确性
然后还有一个技巧是动态开点
就是每次给新点分配个编号,左右儿子拿数组(或指针)指一下就行了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid (l+r>>1)
const int N=1e6+100;
int n,m,tot;
int r[N],a[N];
struct node{int ls,rs,val;
}tr[N*20];
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].val=tr[tr[x].ls].val+tr[tr[x].rs].val;
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].val=a[l];return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].val=v;return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
int ask(int x,int l,int r,int p){if(l==r) return tr[x].val;if(p<=mid) return ask(tr[x].ls,l,mid,p);else return ask(tr[x].rs,mid+1,r,p);
}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);build(r[0],1,n);int t,op,pl,v;for(int i=1;i<=m;i++){scanf("%d%d%d",&t,&op,&pl);if(op==1){scanf("%d",&v);r[i]=r[t];update(r[i],1,n,pl,v);}else{r[i]=r[t];printf("%d\n",ask(r[i],1,n,pl));}}return 0;
}
区间第k大
然后就是这个主席树的经典问题啦
首先从前往后建n个版本的权值线段树
利用前言说的两棵树做差的思想进行查找
就迎刃而解了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid (l+r>>1)
const int N=1e6+100;
int n,m,tot;
int r[N],a[N];
struct node{int ls,rs,val;
}tr[N*20];
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].val=tr[tr[x].ls].val+tr[tr[x].rs].val;
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].val=0;return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].val+=v;return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
int q[N];
int find(int x,int y,int l,int r,int k){if(l==r) return l;if(tr[tr[y].ls].val-tr[tr[x].ls].val>=k){return find(tr[x].ls,tr[y].ls,l,mid,k);}else return find(tr[x].rs,tr[y].rs,mid+1,r,k-(tr[tr[y].ls].val-tr[tr[x].ls].val));
}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]),q[i]=a[i];sort(q+1,q+1+n);int cnt=unique(q+1,q+1+n)-q-1;build(r[0],1,n);for(int i=1;i<=n;i++){a[i]=lower_bound(q+1,q+1+cnt,a[i])-q;r[i]=r[i-1];update(r[i],1,n,a[i],1);}int x,y,k;for(int i=1;i<=m;i++){scanf("%d%d%d",&x,&y,&k);printf("%d\n",q[find(r[x-1],r[y],1,n,k)]);}return 0;
}
练习
然后就是几道练习题!OvO
粟粟的书架
传送门
贪心的想,肯定要从大往小拿
这个二维的范围才到200,做一下前缀和二分答案即可
一维是今天的内容了,其实也和区间第k大差不多
就是先可着右儿子拿,不够再往左走
这样就可以啦
还是比较裸
记得判一下无解
代码
//#include<bits/stdc++.h>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid ((l+r)>>1)
const int N=5e5+100;
int n,m,ask,tot;
int r[N],a[N];
int q[N];
struct node{int ls,rs,sum,siz;
}tr[N*25];
int ceil(int x,int y){return x%y==0?x/y:x/y+1;
}
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].sum=tr[tr[x].ls].sum+tr[tr[x].rs].sum;tr[x].siz=tr[tr[x].ls].siz+tr[tr[x].rs].siz;
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].sum=tr[x].siz=0;return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].siz++;tr[x].sum+=q[l];return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
int find(int x,int y,int l,int r,int k){
// printf("l=%d r=%d lft=%d\n",q[l],q[r],k);if(l==r) return ceil(k,q[l]);if(tr[tr[y].rs].sum-tr[tr[x].rs].sum>=k){return find(tr[x].rs,tr[y].rs,mid+1,r,k);}else return tr[tr[y].rs].siz-tr[tr[x].rs].siz+find(tr[x].ls,tr[y].ls,l,mid,k-(tr[tr[y].rs].sum-tr[tr[x].rs].sum));
}
int x1,y1,x2,y2,h;
void work1(){n=m;for(int i=1;i<=n;i++) scanf("%d",&a[i]),q[i]=a[i];sort(q+1,q+1+n);int cnt=unique(q+1,q+1+n)-q-1;build(r[0],1,cnt);for(int i=1;i<=n;i++){a[i]=lower_bound(q+1,q+1+cnt,a[i])-q;r[i]=r[i-1];update(r[i],1,cnt,a[i],1);}for(int i=1;i<=ask;i++){scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);if(tr[r[y2]].sum-tr[r[y1-1]].sum<h) printf("Poor QLW\n");else printf("%d\n",find(r[y1-1],r[y2],1,cnt,h));}
}
int sum[205][205][1020],num[205][205][1020];
int x[205][205];
int ask_sum(int x1,int y1,int x2,int y2,int k){if(k>1000) return 0;return sum[x2][y2][k]-sum[x1-1][y2][k]-sum[x2][y1-1][k]+sum[x1-1][y1-1][k];
}
int ask_num(int x1,int y1,int x2,int y2,int k){if(k>1000) return 0;return num[x2][y2][k]-num[x1-1][y2][k]-num[x2][y1-1][k]+num[x1-1][y1-1][k];
}
void work2(){for(int i=1;i<=n;i++)for(int j=1;j<=m;j++) scanf("%d",&x[i][j]);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int k=0;k<=1000;k++){sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k]+(x[i][j]>=k)*x[i][j];num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(x[i][j]>=k); }}}for(int i=1;i<=ask;i++){scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);int st=0,ed=1000;while(st<ed){int mmid=(st+ed+1)>>1;if(ask_sum(x1,y1,x2,y2,mmid)>=h) st=mmid;else ed=mmid-1;}if(st==0) printf("Poor QLW\n");else printf("%d\n",ask_num(x1,y1,x2,y2,st+1)+ceil(h-ask_sum(x1,y1,x2,y2,st+1),st));}
}
int main(){scanf("%d%d%d",&n,&m,&ask);if(n==1) work1();else work2();return 0;
}
森林
传送门
本题的两个要求:
- 树合并
- 查询第k大
第一个显然要用LCT (然鹅我不会)
第二个就是主席树了
但是…鱼和熊掌不可兼得啊
我们必须舍弃一个的优秀复杂度
考虑到诸多因素 (including我不会LCT)
我们想到可以用启发式合并的方式把暴力合并的复杂度优化到log
合并时dfs维护一下倍增数组和到根的权值线段树就行了
注意:倍增数组更新时无意义的一定要全部赋值成0!
(不然它在更新前可能是有值的)
启发式合并这东西是真的香
啥算法都没有就直接变暴力为log
一定要有这根弦
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid ((l+r)>>1)
const int N=8e4+100;
int n,m,ask,tot;
int r[N],a[N];
int q[N];
struct edge{int to,nxt;
}p[N<<2];
int fi[N],ccnt=-1;
int cnt;
void addline(int x,int y){p[++ccnt]=(edge){y,fi[x]};fi[x]=ccnt;
}
struct node{int ls,rs,siz;
}tr[80001*500];
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].siz=tr[tr[x].ls].siz+tr[tr[x].rs].siz;
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].siz=0;return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].siz+=v;return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
int lst,x,y,w;
int pl[N][20],mi[20];
int vis[N],dep[N],siz[N],root[N];
void dfs(int x,int f){vis[x]=1;root[x]=f?root[f]:x;siz[x]=1;r[x]=r[f];update(r[x],1,cnt,a[x],1);pl[x][0]=f;for(int i=1;i<=17;i++){pl[x][i]=pl[pl[x][i-1]][i-1];}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f) continue;dep[to]=dep[x]+1;//合并前要先赋一下y的dep!dfs(to,x);siz[x]+=siz[to]; //还要更新siz! }
}
int lca(int x,int y){if(dep[x]<dep[y]) swap(x,y);for(int k=17;k>=0;k--){if(dep[x]-mi[k]<dep[y]) continue;x=pl[x][k];}if(x==y) return x;for(int k=17;k>=0;k--){if(pl[x][k]==pl[y][k]) continue;x=pl[x][k];y=pl[y][k];}return pl[x][0];
}
int find(int x,int y,int u,int v,int l,int r,int k){if(l==r) return q[l];int s=tr[tr[x].ls].siz+tr[tr[y].ls].siz-tr[tr[u].ls].siz-tr[tr[v].ls].siz;if(s>=k) return find(tr[x].ls,tr[y].ls,tr[u].ls,tr[v].ls,l,mid,k);else return find(tr[x].rs,tr[y].rs,tr[u].rs,tr[v].rs,mid+1,r,k-s);
}
int main(){
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);memset(fi,-1,sizeof(fi));mi[0]=1;for(int i=1;i<=17;i++) mi[i]=mi[i-1]<<1;int o;scanf("%d",&o);scanf("%d%d%d",&n,&m,&ask);for(int i=1;i<=n;i++) scanf("%d",&a[i]),q[i]=a[i];sort(q+1,q+1+n);cnt=unique(q+1,q+1+n)-q-1;for(int i=1;i<=n;i++) a[i]=lower_bound(q+1,q+1+cnt,a[i])-q;for(int i=1;i<=m;i++){scanf("%d%d",&x,&y);addline(x,y);addline(y,x);}build(r[0],1,cnt);for(int i=1;i<=n;i++){if(!vis[i]) dfs(i,0);}char op;for(int i=1;i<=ask;i++){scanf(" %c%d%d",&op,&x,&y);if(op=='Q'){scanf("%d",&w);x^=lst;y^=lst;w^=lst;if(x>n||y>n||w>n) break;int anc=lca(x,y);
// printf("lca=%d\n",anc);lst=find(r[x],r[y],r[anc],r[pl[anc][0]],1,cnt,w);printf("%d\n",lst);}else{x^=lst;y^=lst;if(x>n||y>n) break;addline(x,y);addline(y,x);int xx=root[x],yy=root[y];if(siz[xx]<siz[yy]){swap(xx,yy);swap(x,y);}siz[xx]+=siz[yy];dep[y]=dep[x]+1;dfs(y,x);}}return 0;
}
任务查询系统
传送门
比较水啦
直接离线下来在两个端点修改一下对应的权值线段树即可
查询甚至都不用做差了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid ((l+r)>>1)
const int N=1e5+100;
int n,m,tot;
int r[N],a[N];
int q[N];
int num;
struct node{int ls,rs,siz;ll sum;
}tr[N*100];
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].siz=tr[tr[x].ls].siz+tr[tr[x].rs].siz;tr[x].sum=tr[tr[x].ls].sum+tr[tr[x].rs].sum;
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].siz=tr[x].sum=0;return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].siz+=v;tr[x].sum+=v*q[l];return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
ll find(int x,int l,int r,int k){
// printf("x=%d l=%d r=%d siz=%d sum=%d\n",x,l,r,tr[x].siz,tr[x].sum);if(l==r) return 1ll*min(k,tr[x].siz)*q[l];if(tr[tr[x].ls].siz>=k) return find(tr[x].ls,l,mid,k);else return tr[tr[x].ls].sum+find(tr[x].rs,mid+1,r,k-tr[tr[x].ls].siz);
}
struct ask{int pl,v,op;bool operator < (const ask y)const{return pl<y.pl;}
}p[N<<1];
int cnt;
int x[N],y[N],val[N];
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%d%d%d",&x[i],&y[i],&val[i]);q[i]=val[i];}sort(q+1,q+1+n);num=unique(q+1,q+1+n)-q-1;for(int i=1;i<=n;i++){val[i]=lower_bound(q+1,q+1+num,val[i])-q;p[++cnt]=(ask){x[i],val[i],1};p[++cnt]=(ask){y[i]+1,val[i],-1};}sort(p+1,p+1+cnt);build(r[0],1,num);int pl=1;for(int t=1;t<=n;t++){r[t]=r[t-1];while(p[pl].pl==t){update(r[t],1,num,p[pl].v,p[pl].op);pl++;}}ll pre=1,t,a,b,c,w;for(int i=1;i<=m;i++){scanf("%lld%lld%lld%lld",&t,&a,&b,&c);w=(1ll*pre%c*a+b)%c+1;
// printf("w=%d\n",w);pre=find(r[t],1,num,w);printf("%lld\n",pre);}return 0;
}
列队
传送门
显然让学生相对位置不变的对号入座可以是一种最优方案
又由于学生位置两两不同(关键条件!)
所以代价一定是左边一部分是按k+s-1-as算
右边一部分是相反数
所以我们只要找到那个分界点就行了
我的思路是每次递归找第k大判断只往左右儿子的一条递归
这样是两个log的
只有70pts
正解是直接放心大胆算,只要不能确定这一整段的符号就直接往两侧递归
很多人说这就是一个log,但显然它并非递归后一定有一边直接返回
但考虑到k的位置是连续变化的十分稠密,应该很接近单log了
肯定比两个log好
我就是被楼房重建搞魔怔了
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define mid ((l+r)>>1)
const int N=1e6+100;
const int o=1e6;
int n,m,tot;
int r[N],a[N];
int q[N];
int num;
struct tree{int ls,rs,siz,mx;ll sum;
}tr[N*30];
int copy(int x){tr[++tot]=tr[x];return tot;
}
void pushup(int x){tr[x].siz=tr[tr[x].ls].siz+tr[tr[x].rs].siz;tr[x].sum=tr[tr[x].ls].sum+tr[tr[x].rs].sum;tr[x].mx=max(tr[tr[x].ls].mx,tr[tr[x].rs].mx);
}
void build(int &x,int l,int r){x=++tot;if(l==r){tr[x].siz=tr[x].sum=0;return;}build(tr[x].ls,l,mid);build(tr[x].rs,mid+1,r);pushup(x);
}
void update(int &x,int l,int r,int p,int v){x=copy(x);if(l==r){tr[x].siz+=v;tr[x].sum+=v*l;tr[x].mx=l;return;}if(p<=mid) update(tr[x].ls,l,mid,p,v);else update(tr[x].rs,mid+1,r,p,v);pushup(x);
}
struct node{int num;ll tot;
};
ll xigema(int x){x--;return 1ll*(1+x)*x/2;
}
node find(int x,int y,int l,int r,int k,int num){if(tr[y].siz-tr[x].siz==0) return (node){0,0};if(k+num-1<=l) return (node){tr[y].siz-tr[x].siz,tr[y].sum-tr[x].sum};else if(k>=r) return (node){0,-(tr[y].sum-tr[x].sum)};else{int s=tr[tr[y].ls].siz-tr[tr[x].ls].siz;node a=find(tr[x].ls,tr[y].ls,l,mid,k,s);node b=find(tr[x].rs,tr[y].rs,mid+1,r,k+s,num-s);return (node){a.num+b.num,a.tot+b.tot};}
}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);build(r[0],1,o);for(int i=1;i<=n;i++){r[i]=r[i-1];update(r[i],1,o,a[i],1);}int x,y,k;for(int i=1;i<=m;i++){scanf("%d%d%d",&x,&y,&k);int c=y-x+1;node ans=find(r[x-1],r[y],1,o,k,c);ll res=ans.tot-(xigema(c)-2*xigema(c-ans.num))+1ll*(c-2*ans.num)*k;//printf("num=%d tot=%lld\n",ans.num,ans.tot);printf("%lld\n",res);}return 0;
}
/*
5 5
1 5 7 6 2
1 5 2
1 5 3
1 3 9
2 4 2
3 5 5
*/