[Ynoi2018]未来日记

"望月悲叹的最初分块" (妈呀这名字好中二啊(谁叫我要用日本轻小说中的东西命名真是作死))

这里就直接挂csy的题解了,和我的不太一样,但是大概思路还是差不多的,我的做法是和“五彩斑斓的世界”有点类似的维护方法

先考虑如何求区间第k小值。对序列和权值都进行分块,设bi,j表示前j 块中权值在i 块内的数字个数,ci,j 表示前j 块中数字i 的出现次数。那么对于一个询问[l,r] ,首先将零碎部分的贡献加入到临时数组tb 和tc 中,然后枚举答案位于哪一块,确定位于哪一块之后再暴力枚举答案即可在O(√n) 的时间内求出区间第k小值。

接着考虑如何实现区间[l,r]内x 变成y 的功能。显然对于零碎的两块,可以直接暴力重构整块。对于中间的每个整块,如果某一块不含x ,那么无视这一块;否则如果这一块不含y ,那么只需要将x 映射成y ;否则这一块既有x 又有y ,这意味着x 与y 之间发生了合并,不妨直接暴力重构整块。因为有c 数组,我们可以在O(1) 的时间内知道某一块是否有某个数。

考虑什么情况下会发生重构,也就是一个块内发生了一次合并的时候。一开始长度为nn 的序列会提供O(n) 次合并的机会,而每次修改会对零碎的两块各提供一次机会,故总合并次数不超过O(n+m) ,因此当发生合并时直接重构并不会影响复杂度。

那么现在每块中的转换情况只可能是一条条互不相交的链,只需要记录每个初值转换后是什么,以及每个现值对应哪个初值即可。遇到查询的时候,我们需要知道零碎部分每个位置的值,不妨直接重构那两块,然后遍历一遍原数组a即可得到每个位置的值。

在修改的时候,还需要同步维护b 和c 数组,因为只涉及两个权值,因此暴力修改j 这一维也是可以承受的。

总时间复杂度O((n+m)√n) ,空间复杂度O(n√n)

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,BLO=370;
int n,m,a[N],p,vec[N],v2[BLO];
int bl[N],L[BLO],R[BLO];
int s1[BLO][BLO],s2[BLO][N],id[BLO][N],rid[BLO][BLO],pos[N];
int sta[20],ttop=0,lp=0;
char pr[700005];
template <typename T> inline void read(T &x) {
    x=0;register char flag,c=getchar();while(c<‘0‘||c>‘9‘)  flag=c,c=getchar();
    while(c>=‘0‘&&c<=‘9‘)   x=x*10+(c^48),c=getchar(); if(flag==‘-‘) x=-x;
}
template <typename T> inline void print(T x) {
    ttop=0; do { sta[++ttop]=(int)(x%10),x/=10; }while(x);
    while(ttop) pr[lp++]=sta[ttop--]+‘0‘; pr[lp++]=10;
}
inline void reset(int x) { for(register int i=L[x];i<=R[x];++i)    a[i]=rid[x][pos[i]]; }
inline void change(int bel,int x,int y) {
    int uid=id[bel][x];id[bel][y]=uid,rid[bel][uid]=y,id[bel][x]=0;
}
inline void build(int x) {
    for(register int i=1;i<=p;++i)  id[x][rid[x][i]]=0;
    for(register int i=L[x],idx=0;i<=R[x];++i)
        if(!id[x][a[i]])
            id[x][a[i]]=++idx,rid[x][idx]=a[i];
    for(register int i=L[x];i<=R[x];++i)    pos[i]=id[x][a[i]];
}
inline void rebuild(int l,int x,int y) {
    for(register int i=bl[l];i<=bl[n];++i) {
        s2[i][x]+=s2[i-1][x],s2[i][y]+=s2[i-1][y];
        s1[i][bl[x]]+=s1[i-1][bl[x]],s1[i][bl[y]]+=s1[i-1][bl[y]];
    }
}
inline void modify(int l,int r,int x,int y) {
    if(s2[bl[r]][x]-s2[bl[l]-1][x]==0)  return ;
    for(int i=bl[n];i>=bl[l];--i) {
        s2[i][x]-=s2[i-1][x],s2[i][y]-=s2[i-1][y];
        s1[i][bl[x]]-=s1[i-1][bl[x]],s1[i][bl[y]]-=s1[i-1][bl[y]];
    }
    if(bl[l]==bl[r]) {
        reset(bl[l]);
        for(int i=l;i<=r;++i)
            if(a[i]==x) {
                a[i]=y;
                --s2[bl[l]][x],++s2[bl[l]][y];
                --s1[bl[l]][bl[x]],++s1[bl[l]][bl[y]];
            }
        build(bl[l]),rebuild(l,x,y);return ;
    }
    reset(bl[l]),reset(bl[r]);
    for(int i=l;i<=R[bl[l]];++i)
        if(a[i]==x) {
            a[i]=y;
            --s2[bl[l]][x],++s2[bl[l]][y];
            --s1[bl[l]][bl[x]],++s1[bl[l]][bl[y]];
        }
    for(int i=L[bl[r]];i<=r;++i)
        if(a[i]==x) {
            a[i]=y;
            --s2[bl[r]][x],++s2[bl[r]][y];
            --s1[bl[r]][bl[x]],++s1[bl[r]][bl[y]];
        }
    build(bl[l]),build(bl[r]);
    for(int i=bl[l]+1;i<bl[r];++i) {
        if(!s2[i][x])   continue;
        if(s2[i][y]) {
            reset(i);
            for(int j=L[i];j<=R[i];++j)
                if(a[j]==x) {
                    a[j]=y;
                    --s2[i][x],++s2[i][y];
                    --s1[i][bl[x]],++s1[i][bl[y]];
                }
            build(i);
        }
        else {
            s1[i][bl[y]]+=s2[i][x],s1[i][bl[x]]-=s2[i][x];
            s2[i][y]+=s2[i][x],s2[i][x]=0;
            change(i,x,y);
        }
    }
    rebuild(l,x,y);
}
inline int ask(int l,int r,int k) {
    int ans=0,sum=0;
    if(bl[l]==bl[r]) {
        reset(bl[l]);for(int i=l;i<=r;++i)  vec[i]=a[i];
        nth_element(vec+l,vec+l+k-1,vec+r+1),ans=vec[l+k-1];
        for(int i=l;i<=r;++i)   vec[i]=0; return ans;
    }
    reset(bl[l]),reset(bl[r]);
    for(int i=l;i<=R[bl[l]];++i)    ++vec[a[i]],++v2[bl[a[i]]];
    for(int i=L[bl[r]];i<=r;++i)    ++vec[a[i]],++v2[bl[a[i]]];
    for(int i=1;i<=bl[N-1];++i) {
        if(sum+v2[i]+s1[bl[r]-1][i]-s1[bl[l]][i]>=k) {
            for(int j=(i-1)*p+1;j<=i*p;j++) {
                if(vec[j]+s2[bl[r]-1][j]-s2[bl[l]][j]+sum>=k) { ans=j;goto FLAG; }
                else    sum+=vec[j]+s2[bl[r]-1][j]-s2[bl[l]][j];
            }
        }
        else    sum+=v2[i]+s1[bl[r]-1][i]-s1[bl[l]][i];
    }
    FLAG:
    for(int i=l;i<=R[bl[l]];++i)    --vec[a[i]],--v2[bl[a[i]]];
    for(int i=L[bl[r]];i<=r;++i)    --vec[a[i]],--v2[bl[a[i]]];
    return ans;
}
int main() {
    read(n),read(m),p=sqrt(N)+1;
    for(int i=1;i<N;++i) bl[i]=(i-1)/p+1;
    for(int i=1;i<=n;++i) read(a[i]);
    for(int i=1;i<=bl[n];++i)
        L[i]=(i-1)*p+1,R[i]=i*p;R[bl[n]]=n;
    for(int x=1;x<=bl[n];++x)   build(x);
    for(int x=1;x<=bl[n];++x) {
        for(int i=1;i<N;++i)    s2[x][i]=s2[x-1][i];
        for(int i=1;i<=bl[N-1];++i) s1[x][i]=s1[x-1][i];
        for(int i=L[x];i<=R[x];++i) ++s1[x][bl[a[i]]],++s2[x][a[i]];
    }
    for(;m;--m) {
        int opt,x,y;read(opt),read(x),read(y);
        if(opt==1) {int z,w;read(z),read(w),modify(x,y,z,w);}
        else {int k;read(k),print(ask(x,y,k));}
    }
    pr[--lp]=‘\0‘; puts(pr);
    return 0;
}

原文地址:https://www.cnblogs.com/zzrblogs/p/10632566.html

时间: 2024-08-30 14:04:15

[Ynoi2018]未来日记的相关文章

[Ynoi2018]未来日记 - 题解

卡常有风险,code需谨慎 题意: 一个长为\(n\)的序列\(a\),有\(m\)次操作 把区间\([l,r]\)内所有x变成y 查询区间\([l,r]\)内k小值 \((n \leq 100000)\) 题解:lxl所谓的"望月悲叹的第一分块". 其实就是将数列进行值域分块,同时将数列分块,令 \(sum1[i][j]\)表示前\(i\)个块里值域在范围\(( (j-1)*blocksize, j*blocksize ]\)的数的个数,\(sum2[i][j]\)表示前\(i\)个

题解 P4117 【[Ynoi2018]五彩斑斓的世界】

题目链接 我觉得AVX2指令集不够爽,于是写了AVX512.到官网查了一天手册,直接拿下最优解 Solution [Ynoi2018]五彩斑斓的世界 题目大意:给定一个长度为\(n\)的序列.每次将\([l,r]\)内大于\(x\)的数减\(x\).询问\([l,r]\)内\(x\)出现了多少次 分析:还能有啥分析,直接暴力,指令集优化一下就可以了 所有函数都可以在Intel手册查到 首先,既然我们要用指令集,得\(CPU\)资瓷才行 #include <stdint.h> #include

【[Ynoi2018]五彩斑斓的世界】

分块毒瘤题.如果真的想好好练习思维以及代码的话就请不要使用\(\text{WC}\)讲过的黑科技指令集. 正文部分: 由乃题必定分块. 我们可以将同一个块内值相同的用并查集维护起来,因为如果是一个块内相同的值打标机怎么改都一样. 接着谈修改,我们设块内最大值为\(\text{mx}\),修改的值为\(\text{v}\) 如果\(v*2>mx\)的话就改大于\(\text{v}\)的,否则就改小于\(\text{v}\)的 原理是因为如果大于\(\text{v}\)的比较少的话改大的,否则改小的

《新建文本文档》贾瑜

我的笔记本电脑出了点问题,苦笑不得.2010年花了6000元买的机器,重装了约7次系统,最后稳定在现在这个舆论口碑很差的VISTA系统.但我用了大概一年左右,再也没出过问题,虽然慢是慢了点,好在稳定,也就懒得换了.不过我最近发现了一个奇怪的现象——右键点击桌面,新建,文本文档.名为新建文本文档的文件,总会显示有1KB的占用空间,因为文档不是空白的,每个新建的文档,一打开就会写着“你好”两个字.起初我以为是电脑系统的彩蛋,于是找了计算机学院的师兄,他整了大半天也没弄清楚是怎么回事.因为也并不影响实

Spring面向切面 --- AspectJ --- 简单使用

Spring面向切面 --- AspectJ --- 简单使用 昨天回复说说的时候突然写下了下面的一段话:分享一下: <!--******************************************* 其实经过的记忆是可以进行道德化的篡改的,就像夏目漱石的<我是猫>:但是不管怎么改,真正的事实是由每一个人的碎片拼起来的:经济学里计算成本的是在计算将来的成本而不是过去的成本,就像动漫<未来日记>一样:过去发生的事情总是在影响着将来,但是过去发生的事情却不能充当将来下

好看的动漫

什么火影.海贼.死神.妖尾这种超长篇就不多说了,你没说喜欢那类型的,就推荐些我看过觉得不错的各种类型的动漫.... 零之使魔 ——热血+魔法+搞笑后宫的.钉宫巅峰作 进击的巨人——去年最火的番,没看过的话推荐去看,剧情制作都挺精良的. 反叛的鲁鲁修——神作,看了不后悔.剧情也是神展开,人物刻画非常到位 罪恶王冠——鲁鲁修原班人马打造,看着音乐.画面都知道是预算在燃烧.剧情虐心向,结尾有些仓促,但仍然是佳作. 革命机——机战类,鲁鲁修和罪恶王冠编剧大河内一楼的剧本加上sunrise的机战功底,值得

《晨间日记的奇迹》内容概要及读书心得

昨天介绍过<晨间日记的奇迹>一书关于 早起不懒床的几种方法 ,今天来介绍一下这本书的读书心得.这本书主要就是介绍一个在早上写日记的方法,叫做<曼佗罗日记>.这本书对我现在来说帮助不大,只有一定的借鉴作用.但对还没有养成日记习惯的人来说,是一本很好的行动指南.希望有时间的人,尽量花时间把这本书看一遍,然后试着做下去.养成一个写日记的习惯对你的学习和时间管理都有非常大的帮助.如果你实在没有时间来阅读这本书,最好把这篇文章阅读几遍,收藏下来,会对你有帮助的. 在开始之前先介绍一下什么是&

2016年8月6日 九宫格日记

html { height: 100% } body { min-height: 100% } a { } img::selection { background-color: rgba(0, 0, 255, 0.3) } .wiz-table-container { border: 0px !important } .wiz-table-body { border: 0px !important; position: relative; margin: 10px 0 } .wiz-table-

技术宅记录下看过的番

2019年4月 <满脑都是OO的我没办法谈恋爱>擦边球 <川流少女>不善言辞的妹子写字对话的恋爱番 <一个人OO的日子>萌番,讲女主为了完成和姬友的约定交朋友的番 <水果篮子>妹子和被十二生肖附身的男孩子们的故事 <皿三味>河童,欲望 <贤者之孙>男主转生到异世界,成为大魔法师孙子的龙傲天故事 <在世界尽头咏唱恋曲的少女YU-NO>穿越时空的游戏改 <异世界四重奏>骨王,RE:0,素晴,谭雅穿越到一个世界 &l