「Note」您想来点数据结构吗?

大分块系列

最初分块 \(\color{black}{P4119}\)

考虑数列分块+值域分块

数列分块需要维护:

\(nid_{i,j}\) \(fid_i\) \(f_i\)
\(i\) 中数字 \(j\) 的并查集的根 \(i\) 为根的并查集表示的数字 并查集

值域分块需要维护:

\(ncnt_{i,j}\) \(bcnt_{i,j}\)
\(i\) 个块数字 \(j\) 的出现次数 \(i\) 个块中在值域块 \(j\) 中的个数

预处理:

序列分块、值域分块块长,序列、值域每个值对应块。
每个块用 \(nid,fid\) 建立映射,\(f_i\) 连向此块与当前点值相同的第一个值(的下标),若此块中第一次出现当前点值,则考虑对 \(nid,fid\) 赋值。
显著地,扫一遍序列同时现将 \(ncnt,bcnt\) 赋上初值,再进行一遍前缀和。

修改:

碎块直接暴力赋值暴力重构,记得更新前缀和。
整块直接合并 \(x,y\) 两值,若 \(y\) 值不存在则直接将 \(x\) 并查集根节点所代表值改为 \(y\)
整块的前缀和合并更新,扫整块的时候记录一个 \(temp\) 用来一次性更新前缀和,保证复杂度。

查询:

碎块处理需要先访问到序列真实值。
考虑开两个数组维护碎块的每个值出现次数、值域块内值个数。
查询直接先一点点跳整块(要算上碎块维护的值以及整块的值),然后一个个跳数字找到答案。
查询完毕记得清空维护碎块数组,注意要用添加的镜像操作回退,保证复杂度。

整体思路比较清晰,维护时注意细节,考虑每一个变量是否会在操作是变化,再卡卡常即可。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
//IO
int rd()
{
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
//--------------------//
const int N=1e5+5,QN=400,QN2=500;

int n,m;
//--------------------//
struct Dec
{
	struct Block
	{
		int l,r;
		int nid[N];
	}b[QN];
	int ncnt[QN][N],bcnt[QN][QN2];

	int fid[N],f[N];
	int len1,bcnt1,bl1[N];
	int len2,bcnt2,bl2[N];
	int s[N];

	inline int find(int x){return f[x]==x?x:f[x]=find(f[x]);}

	inline void init()
	{
		len1=512;
		len2=256;
		for(register int i=1;i<=1e5;i++)
			bl2[i]=((bl2[i-1]*len2+1)==i?++bcnt2:bcnt2);
        int maxt=0;
		for(register int i=1;i<=n;i++)
		{
			bl1[i]=((bl1[i-1]*len1+1)==i?++bcnt1:bcnt1);
			if(!b[bl1[i]].nid[s[i]])
			{
				b[bl1[i]].nid[s[i]]=i;
				fid[i]=s[i];
			}
			f[i]=b[bl1[i]].nid[s[i]];
			ncnt[bl1[i]][s[i]]++;
			bcnt[bl1[i]][bl2[s[i]]]++;
		}
		for(register int i=1;i<=bcnt1;i++)
		{
			b[i].l=(i-1)*len1+1,b[i].r=min(n,i*len1);
			for(register int j=1;j<=1e5;j++)
				ncnt[i][j]+=ncnt[i-1][j];
			for(register int j=1;j<=bcnt2;j++)
				bcnt[i][j]+=bcnt[i-1][j];
		}
		return;
	}
	inline void cha_pic(int id,int l,int r,int x,int y)
	{
		int xcnt=0;
		//printf("\npic:\n");
		for(register int i=b[id].l;i<=b[id].r;i++)
			s[i]=fid[find(f[i])];
        for(register int i=l;i<=r;i++)
        {
			if(s[i]==x)
			{
				xcnt++;
				s[i]=y;
			}
        }
		for(register int i=b[id].l;i<=b[id].r;i++)
		{
			if(f[i]==i)
			{
				b[id].nid[fid[i]]=0;
				fid[i]=0;
			}
			f[i]=0;
		}
		for(register int i=b[id].l;i<=b[id].r;i++)
		{
			if(!b[id].nid[s[i]])
			{
				b[id].nid[s[i]]=i;
				fid[i]=s[i];
			}
			f[i]=b[id].nid[s[i]];
		}
		for(register int i=id;i<=bcnt1;i++)
		{
			ncnt[i][x]-=xcnt,ncnt[i][y]+=xcnt;
			bcnt[i][bl2[x]]-=xcnt,bcnt[i][bl2[y]]+=xcnt;
		};
		return;
	}

	inline void change(int l,int r,int x,int y)
	{
		if(x==y)
			return;
		int now1=bl1[l],now2=bl1[r];
        if(!(ncnt[now2][x]-ncnt[now1-1][x]))
            return;
		if(now1==now2)
		{
			cha_pic(now1,l,r,x,y);
			return;
		}
		cha_pic(now1,l,b[now1].r,x,y);
		cha_pic(now2,b[now2].l,r,x,y);
		int tem=0;
		for(register int temp,i=now1+1;i<now2;i++)
		{
			temp=ncnt[i][x]-ncnt[i-1][x];
			ncnt[i-1][x]-=tem,ncnt[i-1][y]+=tem;
			bcnt[i-1][bl2[x]]-=tem,bcnt[i-1][bl2[y]]+=tem;
			tem+=temp;
			if(!b[i].nid[y])
			{
				b[i].nid[y]=b[i].nid[x];
				fid[b[i].nid[y]]=y;
			}
			else
			{
				fid[b[i].nid[x]]=0;
				f[b[i].nid[x]]=b[i].nid[y];
			}
			b[i].nid[x]=0;
		}
		if(now1+1<now2)
		{
            for(register int i=now2-1;i<=bcnt1;i++)
            {
                ncnt[i][x]-=tem,ncnt[i][y]+=tem;
                bcnt[i][bl2[x]]-=tem,bcnt[i][bl2[y]]+=tem;
            }
		}
		return;
	}
	int temnc[N],tembc[QN];
	inline int get_ans(int k,int l,int r)
	{
		int now=1;
		while(k>tembc[now]+bcnt[r][now]-bcnt[l-1][now])
			k-=tembc[now]+bcnt[r][now]-bcnt[l-1][now],now++;
		for(register int i=(now-1)*len2+1;i<=min(now*len2,100000);i++)
		{
			k-=temnc[i]+ncnt[r][i]-ncnt[l-1][i];
			if(k<=0)
				return i;
		}
		return 114514;
	}
	inline int query(int l,int r,int k)
	{
		int now1=bl1[l],now2=bl1[r],res=0;
		if(now1==now2)
		{
			for(register int i=l;i<=r;i++)
				s[i]=fid[find(i)],temnc[s[i]]++,tembc[bl2[s[i]]]++;
			res=get_ans(k,1,0);
			for(register int i=l;i<=r;i++)
				temnc[s[i]]--,tembc[bl2[s[i]]]--;
			return res;
		}
        for(register int i=l;i<=b[now1].r;i++)
            s[i]=fid[find(i)],temnc[s[i]]++,tembc[bl2[s[i]]]++;
        for(register int i=b[now2].l;i<=r;i++)
            s[i]=fid[find(i)],temnc[s[i]]++,tembc[bl2[s[i]]]++;
		res=get_ans(k,now1+1,now2-1);
        for(register int i=l;i<=b[now1].r;i++)
            temnc[s[i]]--,tembc[bl2[s[i]]]--;
        for(register int i=b[now2].l;i<=r;i++)
            temnc[s[i]]--,tembc[bl2[s[i]]]--;
		return res;
	}
}D;
//--------------------//
int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=n;i++)
		D.s[i]=rd();
	D.init();
	for(int op,l,r,x,y,i=1;i<=m;i++)
	{
		op=rd();
		if(op==1)
			l=rd(),r=rd(),x=rd(),y=rd(),D.change(l,r,x,y);
		else
			l=rd(),r=rd(),x=rd(),printf("%d\n",D.query(l,r,x));
	}
    return 0;
}

第二分块 \(\color{black}{P4117}\)

与上一道题类似,考虑用并查集维护信息。

\(fid_i\) \(sid_i\) \(cnt_i\) \(mxv\)
\(i\) 为根的并查集代表的数字 数字 \(i\) 的并查集根的编号 数字 \(i\) 出现的次数(包括懒标记) 块内当前最大值

对于整个块一起修改:

\(mxv \ge 2x + lazy\) 时,把小于 \(x\) 的向上合并,并打懒标记表示区间需要减多少。
\(mxv > 2x + lazy\) 时,把大于 \(x\) 的向下合并。

对于碎块修改直接暴力重构即可。

$\text{Code}$:
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned UN;
typedef double DB;
//--------------------//
inline int rd() {
    int ret = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = -f;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        ret = ret * 10 + ch - '0', ch = getchar();
    return ret * f;
}
//--------------------//
const int N = 1e6 + 5, M = 5e5 + 5, V = 5e5 + 5, QN = 1024 + 5;
const int B = 1024;

int n, m, a[N];

struct Que {
    int op, l, r, x;
} q[M];

#define bl(x) (((x) + B - 1) / B)

int len, l, r, mxv, lazy, s[QN], f[QN], fid[QN], sid[V], cnt[V];

inline int find(int x) {return ((x == f[x]) ? x : f[x] = find(f[x]));}

inline void re_mak() {
    mxv = 0;
    for (int i = 1; i <= len; i++) {
        mxv = max(mxv, s[i]), cnt[s[i]]++, fid[i] = s[i];
        if (!sid[s[i]])
            sid[s[i]] = i, f[i] = i;
        else
            f[i] = sid[s[i]];
    }
}

inline void mak_re() {
    for (int i = 1; i <= len; i++) {
        s[i] = fid[find(i)];
        sid[s[i]] = cnt[s[i]] = 0, s[i] -= lazy;
    }
    for (int i = 1; i <= len; i++)
        fid[i] = 0;
    lazy = 0;
}

inline void init(int id) {
    mak_re();
    l = (id - 1) * B + 1, r = min(id * B, n), len = r - l + 1;
    for (int pos = 1, i = l; i <= r; i++, pos++)
        s[pos] = a[i];
    re_mak();
}

inline void merge(int i, int to) {
    cnt[to] += cnt[i], cnt[i] = 0;
    if (!sid[to])
        sid[to] = sid[i], fid[sid[to]] = to;
    else
        f[sid[i]] = sid[to];
    sid[i] = 0;
}

inline void change1(int l, int r, int x) {
    if (mxv < x + lazy || !x)
        return;
    mak_re();
    for (int i = l; i <= r; i++)
        s[i] -= (s[i] > (x + lazy)) * x;
    re_mak();
}

inline void change2(int x) {
    if (!x)
        return;
    if (mxv >= x * 2 + lazy) {
        for (int i = lazy; i <= x + lazy; i++)
            if (sid[i])
                merge(i, i + x);
        lazy += x;
    } else {
        for (int i = lazy + x + 1; i <= mxv; i++)
            if (sid[i])
                merge(i, i - x);
        mxv = min(lazy + x, mxv);
    }
}

inline int query1(int l, int r, int x) {
    if (x + lazy > mxv)
        return 0;
    int res = 0;
    for (int i = l; i <= r; i++)
        res += (fid[find(i)] == x + lazy);
    return res;
}

inline int query2(int x) {
    if (x + lazy <= 5e5)
        return cnt[x + lazy];
    return 0;
}

int acnt, ans[M];
//--------------------//
int main() {
    n = rd(), m = rd();
    for (int i = 1; i <= n; i++)
        a[i] = rd();
    for (int i = 1; i <= m; i++)
        q[i].op = rd(), q[i].l = rd(), q[i].r = rd(), q[i].x = rd();
    int mxb = bl(n);
    for (int i = 1; i <= mxb; i++) {
        init(i), acnt = 0;
        for (int j = 1; j <= m; j++) {
            acnt += (q[j].op == 2);
            if (q[j].op == 1) {
                if (q[j].l <= l && q[j].r >= r)
                    change2(q[j].x);
                else {
                    if (l <= q[j].l && r >= q[j].r)
                        change1(q[j].l - l + 1, q[j].r - l + 1, q[j].x);
                    else if (l <= q[j].l && q[j].l <= r)
                        change1(q[j].l - l + 1, len, q[j].x);
                    else if (l <= q[j].r && r >= q[j].r)
                        change1(1, q[j].r - l + 1, q[j].x);
                }
            } else {
                if (q[j].l <= l && q[j].r >= r)
                    ans[acnt] += query2(q[j].x);
                else {
                    if (l <= q[j].l && r >= q[j].r)
                        ans[acnt] += query1(q[j].l - l + 1, q[j].r - l + 1, q[j].x);
                    else if (l <= q[j].l && q[j].l <= r)
                        ans[acnt] += query1(q[j].l - l + 1, len, q[j].x);
                    else if (l <= q[j].r && r >= q[j].r)
                        ans[acnt] += query1(1, q[j].r - l + 1, q[j].x);
                }
            }
        }
    }
    for (int i = 1; i <= acnt; i++)
        printf("%d\n", ans[i]);
    return 0;
}

第四分块

\(fid_i\) \(sid_{i, j}\) \(val_{i, j}\) \(vid_{i, j}\) \(dis_{i, j, k}\) \(lx_{i, j}\) \(rx_{i, j}\)
\(i\) 为根的并查集代表的数字 \(i\) 中,数字 \(j\) 的并查集根的编号 \(i\) 中,数字 \(j\) 的真实值 \(i\) 中,真实值为 \(j\) 的数字的离散值 \(i\) 中,离散值为 \(j, k\) 的值的距离 \(i\) 中,数字 \(j\) 最左出现位置 \(i\) 中,数字 \(j\) 最右出现位置
posted @ 2023-08-17 18:48  Eon_Sky  阅读(13)  评论(1编辑  收藏  举报