level 1
level 1-1 模板-查询k小值
模板,不再赘述。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 200000
int n,Q;
int a[MAXN+5],b[MAXN+5];
int root[MAXN+5];
int rnum;
struct node
{
int lc,rc;
int sum;
}tree[MAXN*20+5];
void Build(int &x,int l,int r)
{
x=++rnum;
if(l==r)return ;
int mid=(l+r)>>1;
Build(tree[x].lc,l,mid);
Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos)
{
int Nr=++rnum;
tree[Nr]=tree[x];
tree[Nr].sum=tree[x].sum+1;
if(l==r)return Nr;
int mid=(l+r)>>1;
if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos);
else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos);
return Nr;
}
int Query(int Lr,int Rr,int l,int r,int val)
{
int Pv=tree[tree[Rr].lc].sum-tree[tree[Lr].lc].sum;
if(l==r)return l;
int mid=(l+r)>>1;
if(val<=Pv)return Query(tree[Lr].lc,tree[Rr].lc,l,mid,val);
else return Query(tree[Lr].rc,tree[Rr].rc,mid+1,r,val-Pv);
}
int main()
{
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)
{scanf("%d",&a[i]);b[i]=a[i];}
sort(b+1,b+n+1);
int p=unique(b+1,b+n+1)-b-1;
Build(root[0],1,p);
for(int i=1;i<=n;i++)
{
int P=lower_bound(b+1,b+p+1,a[i])-b;
root[i]=Insert(root[i-1],1,p,P);
}
while(Q--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",b[Query(root[l-1],root[r],1,p,k)]);
}
}
level 1-2 [POI2014]couriers
给一个数列,每次询问一个区间内有没有一个数出现次数超过一半
n ≤ 500000 n≤500000 n≤500000
建
n
n
n个版本的权值线段树,每个点统计数值
i
i
i的出现次数。
询问的时候用
r
r
r版本减去
l
−
1
l-1
l−1版本,维护sum递归查找一下即可。
[代码算了…]
level 2
level 2-1 [CQOI2015]任务查询系统
最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分。超级计算机中的任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行),其优先级为Pi。同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同。调度系统会经常向查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个)的优先级之和是多少。特别的,如果Ki大于第Xi秒正在运行的任务总数,则直接回答第Xi秒正在运行的任务优先级之和。上述所有参数均为整数,时间的范围在1到n之间(包含1和n)。
其实这道题本身是不用主席树的。
关键是它要强制在线…
一个十分显然的套路:
运用差分思想,将操作拆成两个部分:在Si位置插入线段树,在Ei+1位置从线段树中删除。
线段树的每个位置代表优先值为
i
i
i的计算机。在线段树上维护优先值之和。
通过主席树保存每个时间的线段树即可。
由于有优先值很大,因此要离散化。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 300000
int n,Q;
int root[MAXN*20+5];
int rnum;
struct As
{
int l,r,val;
}a[MAXN+5];
struct Qry
{
int pos,val,st,Tp;
}qry[MAXN+5];
struct node
{
int lc,rc;
long long cnt,sum;
}tree[MAXN*20+5];
int Hs[MAXN+5];
bool cmp(Qry s1,Qry s2){return s1.Tp<s2.Tp;}
int read()
{
int x=0,f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*f;
}
int Insert(int x,int l,int r,int pos,int val,int F)
{
int Nr=++rnum;
tree[Nr]=tree[x];
tree[Nr].cnt+=F;
tree[Nr].sum+=F*val;
if(l==r)return Nr;
int mid=(l+r)>>1;
if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos,val,F);
else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos,val,F);
return Nr;
}
long long Query(int x,int l,int r,int val)
{
if(l==r)return val*tree[x].sum/tree[x].cnt;
int mid=(l+r)>>1;
int Pv=tree[tree[x].lc].cnt;
if(tree[x].cnt<=val)return tree[x].sum;
if(Pv>=val)return Query(tree[x].lc,l,mid,val);
return Query(tree[x].rc,mid+1,r,val-Pv)+tree[tree[x].lc].sum;
}
int main()
{
n=read(),Q=read();
for(int i=1;i<=n;i++)
{
a[i].l=read(),a[i].r=read(),a[i].val=read();
Hs[i]=a[i].val;
}
sort(Hs+1,Hs+n+1);
int p=unique(Hs+1,Hs+n+1)-Hs-1;
for(int i=1;i<=n;i++)
{
int P=lower_bound(Hs+1,Hs+p+1,a[i].val)-Hs;
qry[i*2-1]=(Qry){P,a[i].val,1,a[i].l};
qry[i*2]=(Qry){P,a[i].val,-1,a[i].r+1};
}
sort(qry+1,qry+n*2+1,cmp);
int Qcnt=1;
for(int i=1;i<=Q;i++)
{
root[i]=root[i-1];
while(i==qry[Qcnt].Tp)
{
root[i]=Insert(root[i],1,p,qry[Qcnt].pos,qry[Qcnt].val,qry[Qcnt].st);
Qcnt++;
}
}
long long ans=1;
while(Q--)
{
int ps,A,B,C;
long long k;
scanf("%d%d%d%d",&ps,&A,&B,&C);
k=(ans*A+B)%C+1;
ans=Query(root[ps],1,p,k);
printf("%lld\n",ans);
}
}
level 2-2 可持久化并查集
用主席树维护fa数组和dep数组。
将每次合并看做是单点修改fa值。
注意这里并不能用路径压缩,因为路径压缩修改的fa值非常多,很容易MLE。
这里可以用启发式合并,使得深度大约在
log
n
\log n
logn左右
由于find的时候要Query,因此还会多一个
log
\log
log
复杂度
O
(
n
log
2
n
)
O(n\log^2 n)
O(nlog2n)
由于实际深度多数小于
log
n
\log n
logn因此常数较小。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 200000
int tp,n,Q,u,v;
int rt[MAXN*40+5];
struct Per_DSU{
int L[MAXN*40+5],R[MAXN*40+5],fa[MAXN*40+5],dep[MAXN*40+5];
int ecnt;
Per_DSU(){ecnt=0;}
void Build(int &p,int l,int r)
{
p=++ecnt;
if(l==r){fa[p]=l;return ;}
int mid=(l+r)>>1;
Build(L[p],l,mid),Build(R[p],mid+1,r);
}
void merge(int p1,int &p2,int l,int r,int pos,int v)
{
p2=++ecnt;L[p2]=L[p1],R[p2]=R[p1];
if(l==r){fa[p2]=v;dep[p2]=dep[p1];return ;}
int mid=(l+r)>>1;
if(pos<=mid)merge(L[p1],L[p2],l,mid,pos,v);
else merge(R[p1],R[p2],mid+1,r,pos,v);
}
void update(int p,int l,int r,int pos)
{
if(l==r){dep[p]++;return ;}
int mid=(l+r)>>1;
if(pos<=mid)update(L[p],l,mid,pos);
else update(R[p],mid+1,r,pos);
}
int Query(int p,int l,int r,int pos)
{
if(l==r)return p;
int mid=(l+r)>>1;
if(pos<=mid)return Query(L[p],l,mid,pos);
else return Query(R[p],mid+1,r,pos);
}
int xfind(int rt,int pos)
{
int np=Query(rt,1,n,pos);
if(fa[np]==pos)return np;
return xfind(rt,fa[np]);
}
}S;
int main()
{
scanf("%d%d",&n,&Q);
S.Build(rt[0],1,n);
for(int i=1;i<=Q;i++)
{
scanf("%d%d",&tp,&u);
if(tp==1)
{
scanf("%d",&v);
int nx,ny;
rt[i]=rt[i-1];
nx=S.xfind(rt[i],u);ny=S.xfind(rt[i],v);
if(S.fa[nx]!=S.fa[ny])
{
if(S.dep[nx]>S.dep[ny])swap(nx,ny);
S.merge(rt[i-1],rt[i],1,n,S.fa[nx],S.fa[ny]);
if(S.dep[nx]==S.dep[ny])S.update(rt[i],1,n,S.fa[ny]);
}
}
if(tp==2)rt[i]=rt[u];
if(tp==3){
scanf("%d",&v);
int nx,ny;
rt[i]=rt[i-1];
nx=S.xfind(rt[i],u);ny=S.xfind(rt[i],v);
if(S.fa[nx]==S.fa[ny])puts("1");
else puts("0");
}
}
}
level 2-3 [SDOI2010]粟粟的书架
其实这题可以不用线段树做…
Task1:
由于
P
i
,
j
≤
1
,
000
,
R
,
C
≤
200
P_{i,j}≤1,000,R, C≤200
Pi,j≤1,000,R,C≤200
考虑直接矩阵前缀和。
定义
c
n
t
[
i
]
[
j
]
[
k
]
cnt[i][j][k]
cnt[i][j][k]为
(
1
,
1
)
−
(
i
,
j
)
(1,1)-(i,j)
(1,1)−(i,j)矩阵中数值大于等于
k
k
k的个数。
s
u
m
[
i
]
[
j
]
[
k
]
sum[i][j][k]
sum[i][j][k]为
(
1
,
1
)
−
(
i
,
j
)
(1,1)-(i,j)
(1,1)−(i,j)矩阵中数值大于等于
k
k
k的数值和。
二分一下
k
k
k即可
Task2:
考虑使用主席树。
做法很显然,基本上和模板一样。
发现也有用莫队做的,但实际上这道题用莫队并不好…
level 3
level 3-1 [CTSC2018]混合果汁
看到CTSC被吓到了…
其实是一道联赛签到题。
看到最小值最大第一直觉二分
d
d
d。
考虑果汁按
d
d
d排序。
题目要求价格乘以容量最大,由于总容量固定,价格肯定是越小越好,那么肯定还要按价格排序。
这是二维的,由于价格只用前缀,明显是一个主席树可以解决的问题。
具体做法就是先让果汁按
d
d
d从大到小排序,依次加入主席树中,这样我们就有了
n
n
n个版本的线段树,接下来二分
d
d
d,在相应版本的线段树上再次二分即可。
注意这题要开long long…
O
(
n
log
2
n
)
O(n \log^2 n)
O(nlog2n)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;
#define MAXN 100000
#define LL long long
#define INF 1000000000000000001
#define MOD 1000000000
#define PB push_back
#define MP make_pair
#define FR first
#define SE second
int n,Q,tp1,tp2,tp3;
int rt[MAXN+5];
int pcnt;
struct juice
{
int d,p,l;
}S[MAXN+5];
struct node
{
int lc,rc;
LL cnt,sum;
}tree[MAXN*20+5];
bool cmp(juice s1,juice s2){return s1.d>s2.d;}
void Build(int &x,int l,int r)
{
x=++pcnt;
if(l==r)return ;
int mid=(l+r)>>1;
Build(tree[x].lc,l,mid);
Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos,LL val)
{
int Nr=++pcnt;
tree[Nr]=tree[x];
tree[Nr].sum=tree[x].sum+1LL*val*pos;
tree[Nr].cnt=tree[x].cnt+val;
if(l==r)return Nr;
int mid=(l+r)>>1;
if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos,val);
else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos,val);
return Nr;
}
LL Query(int x,int l,int r,LL L)
{
//printf("%d %lld->%d %d\n",tree[x].cnt,L,l,r);
if(tree[x].cnt<L)return INF;
if(l==r)return 1LL*L*l;
int mid=(l+r)>>1;
if(tree[tree[x].lc].cnt>=L)return Query(tree[x].lc,l,mid,L);
else return tree[tree[x].lc].sum+Query(tree[x].rc,mid+1,r,L-tree[tree[x].lc].cnt);
}
int main()
{
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&tp1,&tp2,&tp3);
S[i]=(juice){tp1,tp2,tp3};
}
sort(S+1,S+n+1,cmp);
Build(rt[0],1,MAXN);
for(int i=1;i<=n;i++)
rt[i]=Insert(rt[i-1],1,MAXN,S[i].p,S[i].l);
while(Q--)
{
LL G,L;
scanf("%lld%lld",&G,&L);
int l=1,r=n,p=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(Query(rt[mid],1,MAXN,L)<=G)p=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",(p!=0)?S[p].d:-1);
}
}
level 3-2 欧铂瑞特
写了一篇blog