大分块系列

最初分块 \(\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\) 最右出现位置

「Note」您想来点数据结构吗?的更多相关文章

  1. 「NOTE」常系数齐次线性递推

    要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...

  2. 「HNOI2016」数据结构大毒瘤

    真是 \(6\) 道数据结构毒瘤... 开始口胡各种做法... 「HNOI2016」网络 整体二分+树状数组. 开始想了一个大常数 \(O(n\log^2 n)\) 做法,然后就被卡掉了... 发现直 ...

  3. Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

      进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...

  4. 「2014-5-31」Z-Stack - Modification of Zigbee Device Object for better network access management

    写一份赏心悦目的工程文档,是很困难的事情.若想写得完善,不仅得用对工具(use the right tools),注重文笔,还得投入大把时间,真心是一件难度颇高的事情.但,若是真写好了,也是善莫大焉: ...

  5. 「2014-3-18」multi-pattern string match using aho-corasick

    我是擅(倾)长(向)把一篇文章写成杂文的.毕竟,写博客记录生活点滴,比不得发 paper,要求字斟句酌八股结构到位:风格偏杂文一点,也是没人拒稿的.这么说来,arxiv 就好比是 paper 世界的博 ...

  6. 「2014-3-17」C pointer again …

    记录一个比较基础的东东-- C 语言的指针,一直让人又爱又恨,爱它的人觉得它既灵活又强大,恨它的人觉得它太过于灵活太过于强大以至于容易将人绕晕.最早接触 C 语言,还是在刚进入大学的时候,算起来有好些 ...

  7. 一个「学渣」从零开始的Web前端自学之路

    从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...

  8. LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)

    题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...

  9. LOJ #2116 Luogu P3241「HNOI2015」开店

    好久没写数据结构了 来补一发 果然写的时候思路极其混乱.... LOJ #2116 Luogu P3241 题意 $ Q$次询问,求树上点的颜色在$ [L,R]$中的所有点到询问点的距离 强制在线 询 ...

  10. 「ZJOI2018」历史(LCT)

    「ZJOI2018」历史(LCT) \(ZJOI\) 也就数据结构可做了-- 题意:给定每个点 \(access\) 次数,使轻重链切换次数最大,带修改. \(30pts:\) 挺好想的.发现切换次数 ...

随机推荐

  1. 通过 C# 打印Word文档

    Word文档是日常办公和学习中不可或缺的一部分.比如在商务往来中,经常需要打印 Word 文档用于撰写和传递正式的商务信函.合作协议.项目提案等.打印出来的文档便于双方签字盖章,具有法律效力和正式性. ...

  2. 《learn to count everything》论文阅读、实验记录

    <learn to count everything>论文阅读 模式识别这门课最后选了这篇论文汇报,记录一下吧. 参考资料: [论文解读]CVPR2021 | FamNet:密集场景计数统 ...

  3. Laravel11 从0开发 Swoole-Reverb 扩展包(三) - reverb广播驱动使用流程

    前情提要 我们第一节的时候,已经大致介绍reverb,他 是 Laravel 应用程序的第一方 WebSocket 服务器,可将客户端和服务器之间的实时通信直接带到您的指尖.开源且只需一个 Artis ...

  4. 记vue修改数组属性,dom不发生变化的问题

    目录: 目录 目录: 开篇 正确的姿势 为什么 $set 开篇 今天在写vue的时候,出现了一个以前可能没遇到的问题.我利用一个数组记录列表下按钮的启用.禁用状态,但我点击某个列表项按钮后,会修改当前 ...

  5. 安装调用.so文件

    博客地址:https://www.cnblogs.com/zylyehuo/ 使用 pwd 命令找到 .so 文件 首先使用 pwd 命令找到要安装的 .so 文件.通过使用此命令打印当前工作目录来找 ...

  6. cxDBTreeList:最简单的节点图标添加方法

    先在窗体上放ImageList关联到cxDBTreeList,在cxDBTreeList的GetNodeImageIndex事件中写如下: procedure cxDBTreeList1GetNode ...

  7. Opencv环境配置一览

    OpenCV环境配置一览 专业相关,平时经常会使用到opencv的一些函数,目前主要包括Ubuntu系统,Android系统,本篇文章介绍在两个系统下对应的环境配置策略. Ubuntu环境 附上一个很 ...

  8. Ubuntu截屏工具推荐

    Ubuntu截屏工具推荐 本篇博文推荐Ubuntu下的截屏工具Flameshot,可以作为Windows下Snipaste截图工具的平替. GitHub地址:https://github.com/fl ...

  9. python-pandas提取网页内tables(表格类型)数据

    比如,下面网页里大学排行的数据 分析这个页面,表格内的数据是包裹在tables里的 这样就可以使用pandas对数据进行提取并且导出成csv文件,具体代码很简单 import pandas as pd ...

  10. JVM 有那几种情况会产生 OOM(内存溢出)?

    JVM 有哪些情况会产生 OOM(内存溢出)? JVM 的内存溢出(OutOfMemoryError, OOM)是指程序在运行过程中,JVM 无法从操作系统申请到足够的内存,导致程序抛出内存溢出异常. ...