八云蓝自动机-题解
首先,我的代码和前面的大佬们非常的相似(同时也没有注释),So代码部分就不上了,其他大佬们的已经够简单了
好了,接下来进入正题
首先观察一下题面
要求:
-
1 x y:将 \(A_x\) 的值修改为 \(k\)。 -
2 x y:交换 \(A_x\) 与 \(A_y\) 的值。 -
3 x:查询 \(A_x\) 的值。
蒟蒻观察中
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
看到了吗,“交换”,“查询”
看数据范围
1 ≤ m ≤ 2 × \(10^5\) , 1 ≤ l ≤ r ≤ m
ands...
1 ≤ q ≤ 2 × \(10^5\)
好多好多的Q啊!QAQ
所以...
水不过去了[doge]
区间查询,首推
莫队算法
这可是个好算法
那么,接触过莫队的大佬们,分块部分结束了
(也许你也是萌新呢?那这篇题解也许对你很有用)
接下来,就是为什么要像大佬们说的一样,转换操作1为操作2
(而且肉眼可见的,操作3转不了
那么,首先理解(在本题中)区间的转移是怎么运作的
首先是重点,操作2:
现在考虑这样的一个序列N
1 2 3 4 5 6 7
现在对于某一个询问中的第x个操作,要交换第4个和第五个,怎么换?
暴力当然非常简单,swap(N[4],N[5])
众所周知,莫队的核心是区间的伸缩(看这里)
那么,当询问区间右端点左/右移时,直接swap()就行了,右移就换,左移就换回来,还是换
但是,左端点动了呢?还是直接换?不可以!!!
左端点是询问区间的开始,所以左端点要添加、删除操作2的话,会直接影响到本次询问的答案
然后八云蓝算对了也不知道=)
那么怎么做到左端点伸缩的同时修改后面的操作呢?
不妨想一想,怎么在伸缩左端点的时候保留修改呢?
先引进一个数组,在大佬们的题解中叫rev[]
表示现位置的原位置
这样就可以做到保留每一个数字原来的位置
如下:
N:1 2 3 4 5 6 7 -> 1 2 3 5 4 6 7
rev:1 2 3 4 5 6 7 -> 1 2 3 5 4 6 7
代码部分(这里x1,x2写成什么随个人喜好而定)
swap(N[x1],N[x2]);
swap(rev[x1],rev[x2]);
但是,再一次考虑到只有原位置上的数的现坐标没什么用(从上面也看得出来)(处理不了左端点的问题)
那么试着再引进一个数组?
与rev[]相对应的,引进一个pos[]
表示原位置的现位置
这样一来,就可以知道现在这里的数原来在哪里
也就是说,同时修改原位置的现位置和现位置的原位置,就可以完成左端点的操作2增删(现在暂时还不考虑操作3)
如下:
N:1 2 3 4 5 6 7 -> 1 2 3 5 4 6 7
rev:1 2 3 4 5 6 7 -> 1 2 3 5 4 6 7
pos:1 2 3 4 5 6 7 -> 1 2 3 5 4 6 7
现在还没有区别?
接下来右端点右移执行操作2,交换第1个和第4个
N:1 2 3 5 4 6 7 -> 5 2 3 1 4 6 7
rev:1 2 3 5 4 6 7 -> 5 2 3 1 4 6 7
pos:1 2 3 5 4 6 7 -> 4 2 3 5 1 6 7
现在更直观了
但是右端点pos[]的交换和rev[]明显不一样,不然上面第二次操作2结束后rev[]和pos[]会相同
现在考虑pos的转换(由于本题解是萌新友好向所以会讲这东西)
其实也没有多复杂,先交换rev之后,现位置的原坐标已经更新,那么想要找到需要修改的原位置的现坐标,就可以访问现位置的原坐标,也就是
swap(pos[rev[x1]],pos[rev[x2]])
比如说上面的第二次操作,对于5,本来应该是交换pos[1],pos[4]的,但是5这个原位置的现坐标是1
不过还好,现位置的原坐标被改了,那么就可以把4这个修改位置改为5,也就是rev[1],考虑到swap的参数没有先后顺序,这里是对的
那么现在回归原问题,怎么做到左边的伸缩
其实现在答案已经显而易见了,左边的伸缩相当于修改原位置的现坐标,因为是一次询问中第一步的改变(而对于任何一个询问区间的左端点前面的序列状态,rev和pos[]都是单调升的1~n的全排列)
那么,直接
swap(pos[x1],pos[x2])
swap(rev[pos[x1]],rev[pos][x2])
swap(N[pos[x1]],N[pos[x2]])
而原理在上面右端点的操作已经讲过了,这里是一样的
而接下来的操作3就简单不少了
为方便左端点伸缩遇到操作2时对于答案的修改,这里可以再引进一个统计数组add[]
add[]数组的操作下标与rev[]保持一致,因为对象都是现位置上的数
但是这里要注意一点,add[]和N[]不能同时交换,否则这次交换没有意义
然后八云蓝算对了也不知道=)
右端点一如既往的简单,是2就不管它,是3就ans+=N[x],add[x]++
右端点左移符号反过来就行了
现在考虑左端点的伸缩
先说伸长,也就是左端点左移
其实也没多难,左移相当于删去的原来的数,加入新的数
那不就是减掉原来的数的贡献,加入新的数的贡献?
这样就好办了
ans+=N[pos[x1]]*(add[pos[x1]]-add[pos[x2]])-N[pos[x2]]*(add[pos[x1]]-add[pos[x2]])
考虑到这些数据全是unsigned int,如果WA了首先考虑下这里是不是出了什么大问题
与右端点左移类似,左端点右移也是左端点左移的符号反过来(ans-=,后面照着抄)
这样就解决了区间伸缩这个问题
剩下的嘛......
分块,这个不用讲了吧?
分了就是AC!
虽然我的时间好慢,但是能过~~
至于为什么要转换1为2
一通分析下来,操作2已经被玩明白了
只要再在N的范围后面多开一倍存修改就可以完成替换的工作,拿空间换时间
那如果不换呢?
会很复杂,区间的收缩需要保留原来的数,这样一来空间省了吗?没有!
只是白白的增加了写代码的时间\(_{NOIP赛场上做这种事是真的亏}\)
所以这题好,好就好在这个统一操作1和2的办法
不换的代码?我不会\(^{qwq}\)
(其实就是懒的hhhhh)
感谢大佬能看到这里 owo
简单且毫无意义但小的小彩蛋(jiushi:
#include<bits/stdc++.h>
#define v 0;
#define o 0;
using namespace std;
int main()
{
return ~~(o^v^o);
}

浙公网安备 33010602011771号