值域分块进阶

前言

本博文讲述值域分块的进阶用法。由于博主很菜,所以遇到错误欢迎各位在评论区指出。

值域分块入门

模意义查询

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');
	}	
}
posted @ 2024-01-30 23:52  ChiFAN鸭  阅读(35)  评论(0编辑  收藏  举报