值域分块进阶
前言
本博文讲述值域分块的进阶用法。由于博主很菜,所以遇到错误欢迎各位在评论区指出。
模意义查询
Q1
每次给定二元组 \((x,y)\)、模数 \(m\),以及一个区间 \([l,r]\)。求出有多少 \(i\in [l,r]\) 满足 \((a_i+x)\bmod m<(a_i+y)\bmod m\)。
A1
经过 简单的题意转化 我们发现其实是要维护一个集合:
-
插入一个数。
-
查询 \(\bmod m\) 意义下小于等于 \(k\) 的数。
然后先考虑 根号分治 来处理 \(m \leq \sqrt n\) 的情况,显然,可以暴力,那么 \(m > \sqrt n\) 怎么办?
我们发现 \(\bmod m\) 意义下的桶上 一个 区间可以转换成原桶上 \(\frac{n}{m}\) 个区间。
因此可以转化成桶上一共 \(n \sqrt n\) 个区间的查询与 \(n\) 各单点修改。
那么我们就可以用 \(O(1)\) 查询,\(O(\sqrt n)\) 修改的值域分块解决。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+114;
struct Node{
int x,y,M,id,f;//anser[id]+=f*v f = 1 or -1
};
const int top = 100000;
const int B = 556;
vector<Node> A[maxn];//储存离线询问
int a[maxn];
int anser[maxn];//输出答案
struct block{
int pre[1000];
}cnt[1000];
int cnt_pre[1001];
int ans[1001][1001];
int n,q;
inline void change(int x,int val){
int sum = x/B;
x%=B;
if(x==0) sum--,x+=B;
for(int i=x;i<=B;i++) {
cnt[sum].pre[i]+=val;
}
for(int i=sum;i<=B;i++) cnt_pre[i]+=val;
}
inline int query(int l,int r){
if(l>r) return 0;
int lc=l/B;
l%=B;
int rc=r/B;
r%=B;
if(l==0) lc--,l+=B;
if(r==0) rc--,r+=B;
if(lc==rc) return cnt[lc].pre[r]-cnt[rc].pre[l-1];
int res=0;
res+=cnt[lc].pre[B]-cnt[lc].pre[l-1];
res+=cnt[rc].pre[r];
res+=cnt_pre[rc-1]-cnt_pre[lc];
return res;
}
void set_add(int x){//向集合中插入 x
change(x,1);
for(int j=1;j<B;j++){
ans[j][x%j]++;
}
}
inline int set_pre(int m,int k){//mod m 意义下小于等于 k 的数的数量
if(k<0) return 0;
if(m<B)
{
int sum=0;
for(int j=0;j<=k;j++){
sum+=ans[m][j];
}
return sum;
}
else{
int l=m,r=m+k,res=query(1,k);
while(r<top){
res+=query(l,r);
l+=m;
r+=m;
}
if(l<=top)
res+=query(l,top);
return res;
}
}
inline int set_query(int m,int l,int r){//mod m \in [l,r]
if(l>r||l<0||r<0) return 0;
return set_pre(m,r)-set_pre(m,l-1);
}
void scan(){
for(int i=1;i<=n;i++){
set_add(a[i]);
for(int j=0;j<A[i].size();j++){
Node now = A[i][j];
//处理询问 now
if(now.x%now.M==now.y%now.M){
anser[now.id]+=now.f*0;
}
else if(now.x%now.M>now.y%now.M){
int l=now.M-now.x%now.M,r=now.M-now.y%now.M-1;
anser[now.id]+=now.f*set_query(now.M,l,r);
}
else{
int l=now.M-now.y%now.M,r=now.M-now.x%now.M-1;
anser[now.id]+=now.f*(i-set_query(now.M,l,r));
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=q;i++){
int l,r,x,y,m;
cin>>l>>r>>x>>y>>m;
Node R,L;
R.f=1;
R.id=i;
R.M=m;
R.x=x;
R.y=y;
A[r].push_back(R);
L.f=-1;
L.id=i;
L.M=m;
L.x=x;
L.y=y;
A[l-1].push_back(L);
}//询问离线
scan();
for(int i=1;i<=q;i++) cout<<anser[i]<<'\n';
}
替代回滚莫队
我们发现,大部分回滚莫队为了达到删除困难,增加简单的目的都让你维护最大值,但是这个东西也可以用值域分块维护。
Q1
定义一棵树的 \(MAD\) 值为其出现次数大于等于两次的点权最大值,现在给出一棵树,对于每一条边,你要求出将其删除后形成的两棵新树的 \(MAD\) 值的最大值。
注意不是真正删掉边,询问间相互独立。
A1
把问题转化为询问区间 \([l,r]\) 中出现次数大于等于 \(2\) 以及 区间之外 出现次数大于等于 \(2\) 的数的最大值。
出现次数大于等于 \(2\) 十分好维护,真正不好维护的其实是 最大值。
但是假若我们把所有答案用一个集合存储,我们就会发现问题成了维护一个集合:
-
加入/删除一个数。
-
求出最大值。
因此考虑把所有数用一个桶存储,再在桶上分块(值域分块),块内记录出现次数最大值,就可以 \(O(\sqrt n)\) 地查询最大值。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxsq = 500;
const int B = 317;
const int maxn = 2e5+114;
int sq;
vector<int> edge[maxn];
int dfn[maxn];//dfs 序列颜色
int val[maxn];//点的颜色
int mx[maxn],mi[maxn];//子树内 dfs 序最大/最小值
int num;//记录目前 dfs了多少点
map< pair<int,int> ,int> edge_pos;//记录每条边的编号
int id[maxn];//记录点 u-fa[u] 这条路径的编号
int id_dfn[maxn];//dfs 序为 u 的 id[u]
int anser[maxn];//记录答案
int cnt[maxn][2];//序列 内/外 出现次数
map<int,int> lsh;//离散化后映射回来
int n;
inline int pmap(int x){
return x/B;
}
inline int Lmap(int x){
return x*B;
}
inline int Rmap(int x){
return (x+1)*B-1;
}
class Node{
private:
int bl_cnt[maxsq];
int cnt[maxn];
public:
inline void add(int x);
inline void del(int x);
inline int query();
}chifan;
inline void Node::add(int x){
int bl=pmap(x);
cnt[x]++;
bl_cnt[bl]++;
}
inline void Node::del(int x){
int bl=pmap(x);
cnt[x]--;
bl_cnt[bl]--;
}
inline int Node::query(){
for(int i=B+3;i>=0;i--){
if(bl_cnt[i]>0){
int L=Lmap(i),R=Rmap(i);
for(int j=R;j>=L;j--){
if(cnt[j]>0) return j;
}
}
}
return -1;
}
inline void add(int x){
if(cnt[x][1]==2){
chifan.del(x);
}
cnt[x][0]++;
cnt[x][1]--;
if(cnt[x][0]==2){
chifan.add(x);
}
}
inline void del(int x){
if(cnt[x][0]==2){
chifan.del(x);
}
cnt[x][0]--;
cnt[x][1]++;
if(cnt[x][1]==2){
chifan.add(x);
}
}
map<int,int> lwx;//反向映射
inline void dfs(int u,int fa){
id[u]=edge_pos[make_pair(max(u,fa),min(u,fa))];
mi[u]=mx[u]=++num;
lwx[num]=u;
id_dfn[num]=id[u];
dfn[num]=val[u];
for(int v:edge[u]){
if(v==fa) continue;
dfs(v,u);
mx[u]=max(mx[u],mx[v]);
}
}
struct Query{
int l,r,id;//存储询问:左右端点以及编号
}q[maxn];
inline bool cmp(Query a, Query b){
return (a.l/sq)==(b.l/sq) ? a.r<b.r : a.l<b.l;
}
inline void work(){
for(int i=1;i<=n;i++){
q[i].id=id_dfn[i];
q[i].l=mi[lwx[i]];
q[i].r=mx[lwx[i]];
//初始化出现次数
cnt[dfn[i]][1]++;
if(cnt[dfn[i]][1]==2){
chifan.add(dfn[i]);
}
}
sort(q+1,q+n+1,cmp);
int l=1,r=1;
add(dfn[1]);
for(int i=1;i<=n;i++){
while(r<q[i].r){
r++;
add(dfn[r]);
}
while(l>q[i].l){
l--;
add(dfn[l]);
}
while(r>q[i].r){
del(dfn[r]);
r--;
}
while(l<q[i].l){
del(dfn[l]);
l++;
}
anser[q[i].id]=lsh[chifan.query()];
}
}
inline void edge_add(int u,int v,int pos){
edge[u].push_back(v);
edge[v].push_back(u);
edge_pos[make_pair(max(u,v),min(u,v))]=pos;
}
inline void init(){
cin>>n;
sq=sqrt(n);
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
edge_add(u,v,i);
}
vector< pair<int,int> > wyb;//val:id
for(int i=1;i<=n;i++){
int x;
cin>>x;
wyb.push_back(make_pair(x,i));
}
sort(wyb.begin(),wyb.end());
int last=1;
for(int i=0;i<wyb.size();i++){
if(wyb[i].first!=wyb[last].first){
last=i;
}
val[wyb[i].second]=(last+1);
lsh[(last+1)]=wyb[i].first;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
dfs(1,0);
work();
for(int i=1;i<n;i++){
cout<<anser[i]<<'\n';
}
return 0;
}
Q2
给定一个序列,多次询问一段区间 \([l,r]\),求区间中相同的数的最远间隔距离。
序列中两个元素的间隔距离指的是两个元素下标差的绝对值。
A2
考虑把区间中每种元素按出现顺序用链表存下来,更新的时候就查询链头链尾元素下表与链头链尾插入删除。
不难看出这些都是 \(O(1)\) 的,最困难的点还是 最远距离。
因此考虑把每种元素的贡献都用值域分块记录下来,查询再在块上查询即可。
#include<bits/stdc++.h>
using namespace std;
template<class t> inline t read(t &x){
char c=getchar();bool f=0;x=0;
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
if(f) x=-x;return x;
}
template<class t> inline void write(t x){
if(x<0) putchar('-'),write(-x);
else{if(x>9) write(x/10);putchar('0'+x%10);}
}
const int maxn = 2e5+114;
const int maxsq = 664;
const int B = 500;
int anser[maxn];
int a[maxn];
map<int,int> lsh;
int n,m,sq;
int tot;
queue<int> recycle;
int t;
struct List_Node{
int val,Last,Next;
}List[maxn*3];
inline int clone(){
int New;
if(recycle.size()>0){
New=recycle.front();
recycle.pop();
}
else{
New=++t;
}
List[New].val=List[New].Next=List[New].Last=0;
return New;
}
class Deque{
private:
int Begin,End,sz;
public:
inline void push_front(int x);
inline void push_back(int x);
inline void pop_front();
inline void pop_back();
inline int front();
inline int back();
};
inline void Deque::push_back(int x){
if(sz==0){
sz++;
Begin=End=clone();
List[Begin].val=x;
}
else{
sz++;
int New=clone();
List[New].val=x;
List[End].Next=New;
List[New].Last=End;
End=New;
}
}
inline void Deque::push_front(int x){
if(sz==0){
sz++;
Begin=End=clone();
List[Begin].val=x;
}
else{
sz++;
int New=clone();
List[New].val=x;
List[Begin].Last=New;
List[New].Next=Begin;
Begin=New;
}
}
inline void Deque::pop_back(){
if(sz==1){
sz--;
Begin=End=0;
}
else{
sz--;
recycle.push(End);
End=List[End].Last;
}
}
inline void Deque::pop_front(){
if(sz==1){
sz--;
Begin=End=0;
}
else{
sz--;
recycle.push(Begin);
Begin=List[Begin].Next;
}
}
inline int Deque::front(){
return List[Begin].val;
}
inline int Deque::back(){
return List[End].val;
}
Deque wyb[maxn];
int cnt[maxn];
inline int pmap(int x){
return x/B;
}
inline int Lmap(int x){
return x*B;
}
inline int Rmap(int x){
return (x+1)*B-1;
}
class Node{
private:
int bl_cnt[maxsq];
int cnt[maxn];
public:
inline void add(int x);
inline void del(int x);
inline int query();
}chifan;
inline void Node::add(int x){
if(x==0) return ;
int bl=pmap(x);
cnt[x]++;
bl_cnt[bl]++;
}
inline void Node::del(int x){
if(x==0) return ;
int bl=pmap(x);
cnt[x]--;
bl_cnt[bl]--;
}
inline int Node::query(){
for(int i=B+3;i>=0;i--){
if(bl_cnt[i]>0){
int L=Lmap(i),R=Rmap(i);
for(int j=R;j>=L;j--){
if(cnt[j]>0) return j;
}
}
}
return 0;
}
struct Query{
int l,r,id;//存储询问:左右端点以及编号
}q[maxn];
inline bool cmp(Query a, Query b){
if(a.l/sq==b.l/sq){
if((a.l/sq)%2==1) return a.r<b.r;
else return a.r>b.r;
}
else return a.l<b.l;
}
inline int value(int x){//颜色 x 的贡献
if(cnt[x]==0) return 0;
if(wyb[x].back()!=wyb[x].front())
return wyb[x].back()-wyb[x].front();
else
return 0;
}
signed main(){
read(n);
sq=sqrt(n);
for(int i=1;i<=n;i++){
int x;
read(x);
if(lsh[x]==0) lsh[x]=++tot;
a[i]=lsh[x];
}
read(m);
for(int i=1;i<=m;i++){
read(q[i].l);
read(q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r){
r++;
chifan.del(value(a[r]));
wyb[a[r]].push_back(r);
chifan.add(value(a[r]));
cnt[a[r]]++;
}
while(l>q[i].l){
l--;
chifan.del(value(a[l]));
wyb[a[l]].push_front(l);
chifan.add(value(a[l]));
cnt[a[l]]++;
}
while(r>q[i].r){
chifan.del(value(a[r]));
wyb[a[r]].pop_back();
chifan.add(value(a[r]));
cnt[a[r]]--;
r--;
}
while(l<q[i].l){
chifan.del(value(a[l]));
wyb[a[l]].pop_front();
chifan.add(value(a[l]));
cnt[a[l]]--;
l++;
}
anser[q[i].id]=chifan.query();
}
for(int i=1;i<=m;i++){
write(anser[i]);
putchar('\n');
}
}
Q3
日记中记录了连续 \(N\) 天发生的事件,大约每天发生一件。
事件有种类之分。第 \(i\) 天发生的事件的种类用一个整数 \(X_i\)
表示,\(X_i\) 越大,事件的规模就越大。
JOI 教授决定用如下的方法分析这些日记:
-
选择日记中连续的几天 \([L,R]\) 作为分析的时间段;
-
定义事件 \(A\) 的重要度 \(W_A\) 为 \(A\times T_A\),其中 \(T_A\) 为该事件在区间 \([L,R]\) 中出现的次数。
现在,您需要帮助教授求出所有事件中重要度最大的事件是哪个,并输出其重要度。
注意:教授有多组询问。
A3
不难发现还是可以跟上面一样的套路来弄,唯一的问题是贡献太大了,桶存不下。
但是对于每一种数,它的贡献都只有 \(cnt_x\) 种。因此所有贡献一共有 \(\sum_{x} cnt_x = n\) 种,因此考虑把所有贡献离散化,在用哈希表记录映射。
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define int long long
using namespace std;
template<class t> inline t read(t &x){
char c=getchar();bool f=0;x=0;
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48),c=getchar();
if(f) x=-x;return x;
}
template<class t> inline void write(t x){
if(x<0) putchar('-'),write(-x);
else{if(x>9) write(x/10);putchar('0'+x%10);}
}
const int maxn = 2e5+114;
const int maxsq = 664;
const int B = 340;
int anser[maxn];
int a[maxn],hhx[maxn];
map<int,int> lsh;
__gnu_pbds::gp_hash_table<int,int> wqx;
map<int,int> zyb;
int n,m,sq;
int tot,t;
int cnt[maxn];
inline int pmap(int x){
return x/B;
}
inline int Lmap(int x){
return x*B;
}
inline int Rmap(int x){
return (x+1)*B-1;
}
class Node{
private:
int bl_cnt[maxsq];
int cnt[maxn];
public:
inline void add(int x);
inline void del(int x);
inline int query();
}chifan;
inline void Node::add(int x){
if(x==0) return ;
int bl=pmap(x);
cnt[x]++;
bl_cnt[bl]++;
}
inline void Node::del(int x){
if(x==0) return ;
int bl=pmap(x);
cnt[x]--;
bl_cnt[bl]--;
}
inline int Node::query(){
for(int i=B+3;i>=0;i--){
if(bl_cnt[i]>0){
int L=Lmap(i),R=Rmap(i);
for(int j=R;j>=L;j--){
if(cnt[j]>0) return j;
}
}
}
return 0;
}
struct Query{
int l,r,id;//存储询问:左右端点以及编号
}q[maxn];
inline bool cmp(Query a, Query b){
if(a.l/sq==b.l/sq){
if((a.l/sq)%2==1) return a.r<b.r;
else return a.r>b.r;
}
else return a.l<b.l;
}
inline int value(int x,int xzy){//颜色 x 的贡献
return wqx[cnt[x]*xzy];
}
int Cnt[maxn];
void init(){
vector<int> zbz;
for(int i=1;i<=n;i++){
Cnt[a[i]]++;
zbz.push_back(Cnt[a[i]]*hhx[i]);
}
zbz.push_back(0);
sort(zbz.begin(),zbz.end());
for(int v:zbz){
if(wqx[v]==0){
wqx[v]=t,zyb[t]=v;
t++;
}
//cout<<v<<' '<<wqx[v]<<'\n';
}
}
signed main(){
read(n);
read(m);
sq=sqrt(n);
for(int i=1;i<=n;i++){
int x;
read(x);
if(lsh[x]==0) lsh[x]=++tot;
a[i]=lsh[x];
hhx[i]=x;
}
init();
for(int i=1;i<=m;i++){
read(q[i].l);
read(q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r){
r++;
chifan.del(value(a[r],hhx[r]));
cnt[a[r]]++;
chifan.add(value(a[r],hhx[r]));
}
while(l>q[i].l){
l--;
chifan.del(value(a[l],hhx[l]));
cnt[a[l]]++;
chifan.add(value(a[l],hhx[l]));
}
while(r>q[i].r){
chifan.del(value(a[r],hhx[r]));
cnt[a[r]]--;
chifan.add(value(a[r],hhx[r]));
r--;
}
while(l<q[i].l){
chifan.del(value(a[l],hhx[l]));
cnt[a[l]]--;
chifan.add(value(a[l],hhx[l]));
l++;
}
anser[q[i].id]=chifan.query();
}
for(int i=1;i<=m;i++){
write(zyb[anser[i]]);
putchar('\n');
}
}