P4119 Ynoi2018 未来日记

lxl 出的题好 duliu 啊。

感谢来自 fr200110217102 的博客 题解 P4119 【Ynoi2018未来日记】

下标分块+值域分块+并查集

其实一开始的方向应该是尝试线段树或者其它的动态维护的算法,直到时间复杂度和空间复杂度对不上,你才会想到——要分块!

区间第 \(k\) 大

从分块做区间第 \(k\) 大出发,如果用二分以及其它的 \(\log\) 级的算法,单次查询的时间复杂度为 \(O(\sqrt n\log n)\),肯定是直接 T 飞了。

从分块一点的角度考虑,维护每一个数在区间内出现了多少次,可以使用一个二维数组: \(sumc[i][j]=\) 前 \(i\) 块中 \(j\) 出现的次数。对于散块,我们可以开一个临时数组记录,这样子可以快速求出 \(x\) 在查询区间内出现了多少次。

但是这样子需要枚举第 \(k\) 大的数,单次查询的复杂的来到了 \(O(\log n)\)。

然后,本题的精髓——值域分块。

值域分块我们并不需要额外开数组统计在某个数在某块内出现了多少次,而是借助值域分块的思想,优化查询时间。

值域块长为 \(S\),\(sums[i][k]=\) 前 \(i\) 块中,\((k-1)S+1\) 到 \(kS\) 的出现次数之和。

这样在求区间第 \(k\) 大时,我们先枚举答案在值域分块的那一块,在然后再在值域分块内枚举具体的数。枚举到某个数发现出现次数大于 \(k\)​ 了,那么就是它了。

时间复杂度 \(\sqrt V\)。

区间修改

每一个块内,用并查集把数字相同的点缩在一起,维护一个 \(rt[i][x]\) 表示第 \(i\) 块内某个值为 \(x\) 的数的位置。

整块修改

\(x\) 变 \(y\) 时,如果 \(rt[i][x]\) 不存在,无需更新 \(sums\) 和 \(sumc\) 即可。

如果 \(rt[i][x]\) 存在,分为两种情况:

1.\(rt[i][y]\) 存在,使 \(fa[rt[i][x]]=rt[i][y]\)。

2.\(rt[i][y]\) 不存在,使 \(rt[i][y]=rt[i][x]\) 并修改 \(a[rt[i][x]]\)​ 的值。

然后,更新 \(sums\) 和 \(sumc\) 即可。

散块修改

由于并查集的特殊结构,不能用类似整块的修改方式。但,这是散块。

直接强行将此块中 \(rt[i][x]\) 和 \(rt[i][y]\)​ 所在的并查集打散重新构建。

然后,更新 \(sums\) 和 \(sumc\) 即可。

单次修改复杂度在 \(O(\sqrt n)\) 级别。

另外由于空间限制,块长取 \(\sqrt n\) 会炸,所以这里块长取 \(600\)。

CODE

#include<bits/stdc++.h>
using namespace std; const int maxn=1e5+5,maxm=170,S=320; int n,m,k,sz=600,vsz=317,mxv=1e5;
int a[maxn];
int L[maxm],R[maxm],bel[maxn];//bel 值的对应分块编号 int fa[maxn],rt[maxm][maxn];
inline int fr(int x){return fa[x]==x?x:fa[x]=fr(fa[x]);}
int cnt[maxm][maxn],sumc[maxm][maxn],sums[maxm][S]; void build(int p)
{
for(int i=L[p];i<=R[p];i++)
{
if(!rt[p][a[i]]) rt[p][a[i]]=i;
else fa[i]=rt[p][a[i]];
++cnt[p][a[i]];
}
}
int stk[maxn];
inline void updata(int p,int l,int r,int x,int y)//散块更新
{
int tmp=0,top=0;
rt[p][x]=rt[p][y]=0;
for(int i=L[p];i<=R[p];i++)
{
a[i]=a[fr(i)];
if(a[i]==x||a[i]==y) stk[++top]=i;
}
for(int i=l;i<=r;i++) if(a[i]==x) a[i]=y,++tmp;
for(int i=1;i<=top;i++) fa[stk[i]]=stk[i];
for(int i=1;i<=top;i++)
{
int t=stk[i];
int w=a[t];
if(!rt[p][w]) rt[p][w]=t;
else fa[t]=rt[p][w];
}
cnt[p][x]-=tmp,cnt[p][y]+=tmp;
for(int i=p;i<=k;i++)
{
sumc[i][x]-=tmp,sumc[i][y]+=tmp;
if(bel[x]!=bel[y])
sums[i][bel[x]]-=tmp,sums[i][bel[y]]+=tmp;
}
} int c[maxn],s[S];
int main()
{
scanf("%d%d",&n,&m);
k=(n-1)/sz+1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),fa[i]=i;
for(int i=1;i<=mxv;i++) bel[i]=(i-1)/vsz+1;
for(int i=1;i<=k;i++)
{
L[i]=(i-1)*sz+1,R[i]=min(i*sz,n);
build(i);
for(int j=1;j<=vsz;j++) sums[i][j]=sums[i-1][j];
for(int j=1;j<=mxv;j++) sumc[i][j]=sumc[i-1][j]+cnt[i][j];
for(int j=L[i];j<=R[i];j++) ++sums[i][bel[a[j]]];
} while(m--)
{
int op;
scanf("%d",&op);
if(op==1)
{
int l,r,x,y;
scanf("%d%d%d%d",&l,&r,&x,&y);
if(x==y) continue;
int lb=(l-1)/sz+1,rb=(r-1)/sz+1;
if(lb==rb) updata(lb,l,r,x,y);
else
{
updata(lb,l,R[lb],x,y);
updata(rb,L[rb],r,x,y);
int tmp,tmps=0;
for(int i=lb+1;i<rb;i++)
{
if(rt[i][x])
{
if(!rt[i][y]) rt[i][y]=rt[i][x],a[rt[i][x]]=y;
else fa[rt[i][x]]=rt[i][y];
rt[i][x]=0,tmp=cnt[i][x],tmps+=tmp;
cnt[i][y]+=tmp,cnt[i][x]=0;
}
sumc[i][x]-=tmps,sumc[i][y]+=tmps;
if(bel[x]!=bel[y]) sums[i][bel[x]]-=tmps,sums[i][bel[y]]+=tmps;
}
for(int i=rb;i<=k;i++)
{
sumc[i][x]-=tmps,sumc[i][y]+=tmps;
if(bel[x]!=bel[y]) sums[i][bel[x]]-=tmps,sums[i][bel[y]]+=tmps;
}
}
}
else
{
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
int lb=(l-1)/sz+1,rb=(r-1)/sz+1;
if(lb==rb)
{
for(int i=l;i<=r;i++) a[i]=a[fr(i)],++c[a[i]],++s[bel[a[i]]];
int vl,vr,tmp=0;
for(int i=1;i<=vsz;i++)
{
tmp+=s[i];
if(tmp>=x){tmp-=s[i],vl=(i-1)*vsz+1,vr=i*vsz;break;}
}
for(int i=vl;i<=vr;i++)
{
tmp+=c[i];
if(tmp>=x){printf("%d\n",i);break;}
}
for(int i=l;i<=r;i++) --c[a[i]],--s[bel[a[i]]];
}
else
{
for(int i=l;i<=R[lb];i++)
{
a[i]=a[fr(i)];
++c[a[i]],++s[bel[a[i]]];
}
for(int i=L[rb];i<=r;i++)
{
a[i]=a[fr(i)];
++c[a[i]],++s[bel[a[i]]];
}
int vl,vr,tmp=0;
for(int i=1;i<=vsz;i++)
{
tmp+=s[i]+sums[rb-1][i]-sums[lb][i];
if(tmp>=x){tmp-=s[i]+sums[rb-1][i]-sums[lb][i];vl=(i-1)*vsz+1,vr=i*vsz;break;}
}
for(int i=vl;i<=vr;i++)
{
tmp+=c[i]+sumc[rb-1][i]-sumc[lb][i];
if(tmp>=x){printf("%d\n",i);break;}
}
for(int i=l;i<=R[lb];i++) --c[a[i]],--s[bel[a[i]]];
for(int i=L[rb];i<=r;i++) --c[a[i]],--s[bel[a[i]]];
}
}
}
}

P4119 Ynoi2018 未来日记的更多相关文章

  1. [Ynoi2018]未来日记

    "望月悲叹的最初分块" (妈呀这名字好中二啊(谁叫我要用日本轻小说中的东西命名真是作死)) 这里就直接挂csy的题解了,和我的不太一样,但是大概思路还是差不多的,我的做法是和“五彩 ...

  2. [Ynoi2018]未来日记(分块)

    分块神题. 看了一会儿题解,看懂了思路,然后写了两个小时,调了一个多小时,好多地方写错了. 我们考虑对序列和值域都分块.\(sum1[i][j]\) 表示前 \(i\) 个块,第 \(j\) 块值域有 ...

  3. bzoj5000+的洛谷题号

    前言 闲得没事把 bzoj5000+ 在 Luogu 上可找到的题面整理了一下-- 对于我,bzoj 连账号都没有,所以肯定是不清楚 bzoj 题目总数的--因此其实就是手动翻查. 工作量很大,基本不 ...

  4. 「Ynoi2018」未来日记

    「Ynoi2018」未来日记 区间x->y,kth值... 不管了,先序列分块... 查询 第k值,假定知道每个数的权值,对值域分块. 对于整块,维护前\(i\)个块当中,值域在\(j\)块里以 ...

  5. ynoi2018

    题解: 全分块是啥操作啊.. 而且都好难.. 1.未来日记 这个比较简单 对每个块开个线段树维护权值 $n\sqrt{n}logn$ 这个会炸空间 并不能做... 但还是说一下做法 首先考虑分块 然后 ...

  6. [Ynoi2018]五彩斑斓的世界

    题目描述 二阶堂真红给了你一个长为n的序列a,有m次操作 1.把区间[l,r]中大于x的数减去x 2.查询区间[l,r]中x的出现次数 题解 做YNOI真**爽... 我们发现这道题的操作非常诡异,把 ...

  7. [Ynoi2018]末日时在做什么?有没有空?可以来拯救吗?

    这道题真的超级...毒瘤 + 卡常 + 耗 RP 啊... 传送门 noteskey 题解看 shadowice 大仙 的 code 如果发现自己 T 掉了,别心急,洗把脸再交一遍试试... //by ...

  8. 洛谷P4117 [Ynoi2018]五彩斑斓的世界 [分块,并查集]

    洛谷 Codeforces 又是一道卡常题-- 思路 YNOI当然要分块啦. 分块之后怎么办? 零散块暴力,整块怎么办? 显然不能暴力改/查询所有的.考虑把相同值的用并查集连在一起,这样修改时就只需要 ...

  9. 【bzoj4810】【ynoi2018】由乃的玉米田

    4810: [Ynoi2017]由乃的玉米田 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 1090  Solved: 524[Submit][Sta ...

  10. 【洛谷5398】[Ynoi2018]GOSICK(二次离线莫队)

    题目: 洛谷 5398 当我刚学莫队的时候,他们告诉我莫队能解决几乎所有区间问题: 现在,当我发现一个区间问题似乎难以用我所了解的莫队解决的时候,他们就把这题的正解叫做 XXX 莫队.--题记 (以上 ...

随机推荐

  1. Game on Sum--组合数学--DP

    \(Codeforces-Round 767\) (Div. 2) F2. \(Game \ on \ Sum\) \(HERE\) 题意 \(QZS\) 和 \(HANGRY\) 玩游戏. 游戏共有 ...

  2. TwinCAT3 - 实现自己的Tc2_SerialCom

    目录 1,前言 2,原生Tc2_SerialCom简单使用 3,实现自己的Tc2_SerialCom 3.1,EL6inData22B,EL6outData22B 3.2,ComBuffer 3.3, ...

  3. Python 字符串格式化输出

    数字 n: int = 1000000000 print(f'{n:_}') # 1_000_000_000 print(f'{n:,}') # 1,000,000,000 对齐 var: str = ...

  4. Mac上HomeBrew安装及换源教程

    Mac上HomeBrew安装及换源教程 Mac的Mac OS系统来源于Unix系统,得益于此Mac系统的使用类似于Linux,因此Linux系统中的包管理概念也适用于Mac,而HomeBrew便是其中 ...

  5. ptmalloc2涉及的基础知识与基本数据结构

    随笔来源:ctfwiki CSDN 本随笔只为记录分析总结的自己学习的结论,方便未来回顾,以及为他人提供一个理解的思路,不保证正确.如有谬误,请大家指出. 1.堆相关的操作 malloc:返回对应大小 ...

  6. webpack高版本抽离css样式报错[已解决]

    全局安装的webpack版本是5.51.1,webpack-cli是4.9的版本; 本来想用 extract-text-webpack-plugin 的,但是报错了,查了一下文档 发现,已经不支持新版 ...

  7. Mongodb入门4

    今天学习一下MongoDB数据库的索引. 因为养的鱼生病了,所以抽空晚上学习记录一下. 这里借用一下菜鸟教程的原文: 索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描 ...

  8. Google reCAPTCHA

    Spam from Website Enquiry 网站一般上都会有 Contact Us 页面. 里头有一个表格, 访客可以通过提交表格发出对产品和服务的讯问. 本来是一个质询功能, 但就是有坏人利 ...

  9. 音视频入门-4-ffmpeg命令快速体验音视频开发/ ffmpeg编译过程经历的99八十一难

    <1>我的实验所使用的视频文件告知读者 1. 这是我在ubuntu环境上实验使用的视频文件, 我在windows上查看了详细信息,然后拖进ubuntu内,重命名为video-test.mp ...

  10. [TK] 理想的正方形

    题目描述 有一个整数组成的矩阵,现请你从中找出一个指定边长的正方形区域,使得该区域所有数中的最大值和最小值的差最小. 题目分析 其实这道题和滑动窗口很像,而滑动窗口使用优先队列解决. 我们都知道优先队 ...