「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\) 最右出现位置 |
「Note」您想来点数据结构吗?的更多相关文章
- 「NOTE」常系数齐次线性递推
要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...
- 「HNOI2016」数据结构大毒瘤
真是 \(6\) 道数据结构毒瘤... 开始口胡各种做法... 「HNOI2016」网络 整体二分+树状数组. 开始想了一个大常数 \(O(n\log^2 n)\) 做法,然后就被卡掉了... 发现直 ...
- Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门
进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...
- 「2014-5-31」Z-Stack - Modification of Zigbee Device Object for better network access management
写一份赏心悦目的工程文档,是很困难的事情.若想写得完善,不仅得用对工具(use the right tools),注重文笔,还得投入大把时间,真心是一件难度颇高的事情.但,若是真写好了,也是善莫大焉: ...
- 「2014-3-18」multi-pattern string match using aho-corasick
我是擅(倾)长(向)把一篇文章写成杂文的.毕竟,写博客记录生活点滴,比不得发 paper,要求字斟句酌八股结构到位:风格偏杂文一点,也是没人拒稿的.这么说来,arxiv 就好比是 paper 世界的博 ...
- 「2014-3-17」C pointer again …
记录一个比较基础的东东-- C 语言的指针,一直让人又爱又恨,爱它的人觉得它既灵活又强大,恨它的人觉得它太过于灵活太过于强大以至于容易将人绕晕.最早接触 C 语言,还是在刚进入大学的时候,算起来有好些 ...
- 一个「学渣」从零开始的Web前端自学之路
从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...
- LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)
题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...
- LOJ #2116 Luogu P3241「HNOI2015」开店
好久没写数据结构了 来补一发 果然写的时候思路极其混乱.... LOJ #2116 Luogu P3241 题意 $ Q$次询问,求树上点的颜色在$ [L,R]$中的所有点到询问点的距离 强制在线 询 ...
- 「ZJOI2018」历史(LCT)
「ZJOI2018」历史(LCT) \(ZJOI\) 也就数据结构可做了-- 题意:给定每个点 \(access\) 次数,使轻重链切换次数最大,带修改. \(30pts:\) 挺好想的.发现切换次数 ...
随机推荐
- 通过 C# 打印Word文档
Word文档是日常办公和学习中不可或缺的一部分.比如在商务往来中,经常需要打印 Word 文档用于撰写和传递正式的商务信函.合作协议.项目提案等.打印出来的文档便于双方签字盖章,具有法律效力和正式性. ...
- 《learn to count everything》论文阅读、实验记录
<learn to count everything>论文阅读 模式识别这门课最后选了这篇论文汇报,记录一下吧. 参考资料: [论文解读]CVPR2021 | FamNet:密集场景计数统 ...
- Laravel11 从0开发 Swoole-Reverb 扩展包(三) - reverb广播驱动使用流程
前情提要 我们第一节的时候,已经大致介绍reverb,他 是 Laravel 应用程序的第一方 WebSocket 服务器,可将客户端和服务器之间的实时通信直接带到您的指尖.开源且只需一个 Artis ...
- 记vue修改数组属性,dom不发生变化的问题
目录: 目录 目录: 开篇 正确的姿势 为什么 $set 开篇 今天在写vue的时候,出现了一个以前可能没遇到的问题.我利用一个数组记录列表下按钮的启用.禁用状态,但我点击某个列表项按钮后,会修改当前 ...
- 安装调用.so文件
博客地址:https://www.cnblogs.com/zylyehuo/ 使用 pwd 命令找到 .so 文件 首先使用 pwd 命令找到要安装的 .so 文件.通过使用此命令打印当前工作目录来找 ...
- cxDBTreeList:最简单的节点图标添加方法
先在窗体上放ImageList关联到cxDBTreeList,在cxDBTreeList的GetNodeImageIndex事件中写如下: procedure cxDBTreeList1GetNode ...
- Opencv环境配置一览
OpenCV环境配置一览 专业相关,平时经常会使用到opencv的一些函数,目前主要包括Ubuntu系统,Android系统,本篇文章介绍在两个系统下对应的环境配置策略. Ubuntu环境 附上一个很 ...
- Ubuntu截屏工具推荐
Ubuntu截屏工具推荐 本篇博文推荐Ubuntu下的截屏工具Flameshot,可以作为Windows下Snipaste截图工具的平替. GitHub地址:https://github.com/fl ...
- python-pandas提取网页内tables(表格类型)数据
比如,下面网页里大学排行的数据 分析这个页面,表格内的数据是包裹在tables里的 这样就可以使用pandas对数据进行提取并且导出成csv文件,具体代码很简单 import pandas as pd ...
- JVM 有那几种情况会产生 OOM(内存溢出)?
JVM 有哪些情况会产生 OOM(内存溢出)? JVM 的内存溢出(OutOfMemoryError, OOM)是指程序在运行过程中,JVM 无法从操作系统申请到足够的内存,导致程序抛出内存溢出异常. ...