「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\) 最右出现位置 |