Ynoi2016镜中的昆虫
[Ynoi 2016] 镜中的昆虫
简化题意
给定长为 \(n\) 序列 \(a\) , 两种操作 \(m\) 次:
1 l r x
: 将 \([l , r]\) 修改为 \(x\)
2 l r
: 查询 \([l , r]\) 出现了多少种不同的数
\(n , m \le 10 ^ 5\)
题解
\(A\) 这道题还是很不容易的,起码用了几天的时间。思路也是换了又换,从分块变成 \(CDQ\) 调了一整天才调完。
首先我们考虑不带修咋做。
显然需要维护 \(Pre\) 数组 , \(Pre_i\) 表示 \(\max\{j\} \ \ \ (1 \le j < i \ \land \ a_j = a_i)\)
其实就是和 \(a_i\) 一样的,在 \(i\) 左边,离 \(i\) 最近的位置。
那其实考虑一个简单的结论:
\]
其实啥意思呢,就是说他前面的数必须在 \(l\) 左边才行如果一个数的 \(Pre\) 在 \(l\) 右边,如果记上他的话计算就会重复。
然后我们就可以把 \(n\) 个数按 \(Pre\) 排序,把询问按 \(l\) 排序那么对于一个询问来说,答案就是上述结论的式子。
但是由于我们排序了,拿一个树状数组维护前缀和,每个答案就是前面所有 \(Pre_i < l\) 且 \(i \in [l , r]\) 数的数量。
考虑使用双指针,比较简单。
考虑单点修咋做。
如果我们刚刚开始还按照上面的做法来做,那我们就是要考虑计算每一个修改对答案的贡献。
这是啥意思呢,就比如说原来是 1 , 2 , 3 , 4
, 前三个改成了 \(1\) , 那么贡献为 \(-3\) .
我们发现一次修改只会改变两个点的 \(Pre\) 值,分别是 \(i\) 和 \(j (Pre_j = i)\)
那其实来优化这个过程,我们使用 \(CDQ\) 分治。
假设我们目前待处理区间为 \((l , r)\) , 那么我们就需要计算 \((l , mid)\) 里的修改对 \((mid + 1 , r)\) 里的查询的影响。
对于修改把 \(x\) 改成 \(y\) ,
其实就是把点 \((i , x)\) 改成 \(-1\) , \((i , y)\) 改成 \(1\) , 再与刚开始的东西相加,发现正好。
这个东西叫做带修二维数点。时间复杂度为 \(O(n \log^2 n)\)
然后学到一个类似本题计算左边修改对右边查询的贡献的题时的做法。
就是分开来做, \((l_1 , r_1]\) 表示修改区间 , \((l_2 , r_2]\) 表示查询区间, \((L , R]\) 表示总数区间。
递归时传参这样:
void solve(int l1 , int r1 , int l2 , int r2 , int L , int R) {
if (l1 == r1 || l2 == r2) return ;
int mid1 = l1 , mid2 = l2 , mid = (L + R) >> 1 ;
while (mid1 < r1 && tmp1[mid1 + 1].id <= mid) ++ mid1 ;
while (mid2 < r2 && tmp2[mid2 + 1].id <= mid) ++ mid2 ;
solve(l1 , mid1 , l2 , mid2 , L , mid) ; solve(mid1 , r1 , mid2 , r2 , mid , R) ;
}
然后做完了。
考虑区间修改。
证明结论:
对于长度为 \(n\) 序列, \(m\) 次操作, \(Pre_i\) 被改动的总次数是 \(O(n + m)\) 级别的.
其实还是很好证的。考虑将连续的颜色块区间看成一个点。
因为同一个块中,除了块头, \(Pre_i = i - 1\) 恒成立.
对于 \(m\) 次操作,那就会产生 \(m\) 个新点。在块左块右产生了两个新块被合并,自然就有两个新块新产生。
中间的点全部删去。
所以最多每次产生 \(3m\) 个点,每个点必最多被删除一次,所以改动数量为 \(O(n + m)\) 级别。
那也就是说对于本次没改的点,在本区间内,显然早就被改过,且值一直不变。
那就意味着 \(-1\) 或 \(1\) 的这个位置的影响早就被扔进来了,不用管了。
对于这种情况显然直接上了。
\(Code\)
CODE
#include <bits/stdc++.h>
#ifdef linux
#define getchar getchar_unlocked
#define putchar putchar_unlocked
#endif
using namespace std ;
typedef long long ll ;
const int N = 3e5 + 100 ;
namespace Fast_IO {
inline int read() {
int x = 0 , f = 1 ;
char c = getchar() ;
while (c < '0' || c > '9') {
c = getchar() ;
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * f ;
}
void write(int x) {
if (x / 10) write(x / 10) ;
putchar(x % 10 + '0') ;
}
} using namespace Fast_IO ;
struct Asker {
int opt , l , r , x ;
} b[N] ;
struct Node1 {
int id , pos , Pre , val ;
} tmp1[N << 2] ;
struct Node2 {
int ider , id , l , r ;
} tmp2[N] ;
#define iter2 set <node> :: iterator
#define iter1 set <Node> :: iterator
int n , m , a[N] , maxn , pi[N << 2] , cnt ;
int Pre[N] , front[N] ;
class Binary_Index_Array {
#define lowbit(x) (x & (- x))
private:
int t[N] ;
public:
void add(int pos , int val) {
while (pos <= n) {
t[pos] += val ;
pos += lowbit(pos) ;
}
}
int Query(int pos) {
int ans = 0 ;
while (pos > 0) {
ans += t[pos] ;
pos -= lowbit(pos) ;
}
return ans ;
}
void clear() {
memset(t , 0 , sizeof(t)) ;
}
void Deleted(int pos) {
while (pos <= n) {
t[pos] = 0 ;
pos += lowbit(pos) ;
}
}
#undef lowbit
} tree ;
struct Node {
int l , r ;
mutable int color ;
Node(int L , int R = 0 , int COLOR = 0) : l(L) , r(R) , color(COLOR) {}
bool operator < (const Node a) const {
return l < a.l ;
} ;
} ; set <Node> s ;
struct node {
mutable int l , r ;
node(int L , int R = 0) : l(L) , r(R) {}
bool operator < (const node a) const {
return l < a.l ;
} ;
} ; set <node> t[N] ;
iter1 Split(int pos) {
iter1 it = s.lower_bound(Node(pos)) ;
if (it != s.end() && it->l == pos) return it ;
it -- ;
int L = it->l , R = it->r , VAL = it->color ;
s.erase(it) ;
t[VAL].erase(node(t[VAL].lower_bound(node(L , R))->l)) ;
t[VAL].insert(node(L , pos - 1)) ;
s.insert(Node(L , pos - 1 , VAL)) ;
if (R < pos) return s.end() ;
t[VAL].insert(node(pos , R)) ;
return s.insert(Node(pos , R , VAL)).first ;
}
int tot1 , tot2 , total ;
void Modify(int pos , int val) {
tmp1[++ tot1] = {++ total , pos , Pre[pos] , -1} ;
tmp1[++ tot1] = {++ total , pos , Pre[pos] = val , 1} ;
}
void Assign(int l , int r , int color , int id) {
iter1 it1 = Split(r + 1) ; iter1 it2 = Split(l) ;
int Fir = it2->l ;
for (iter1 j = it2 ; j != it1 ; ++ j) {
iter2 Next = t[j->color].upper_bound(node(j->l)) ;
if (Next != t[j->color].end()) {
Modify(Next->l , Pre[j->l]) ;
}
Modify(j->l , j->l - 1) ;
int VAL = j->color , L = j->l , R = j->r ;
t[VAL].erase(t[VAL].lower_bound(node(L , R))) ;
}
iter2 Next = t[color].upper_bound(node(l)) ;
if (Next != t[color].end()) {
Modify(Next->l , r) ;
}
auto j = t[color].lower_bound(node(l)) ;
if (j == t[color].begin()) {
Modify(Fir , 0) ;
} else {
-- j ;
Modify(Fir , j->r) ;
}
s.erase(it2 , it1) ;
s.insert(Node(l , r , color)) ;
t[color].insert(node(l , r)) ;
}
int fir[N] , sec[N] ;
int Ans[N] ; pair <int , int> nPre[N] ;
struct PAIRAIR {
int l , r , id ;
} c[N] ; int num ;
void solve(int l1 , int r1 , int l2 , int r2 , int L , int R) {
if (l1 == r1 || l2 == r2) return ;
int mid1 = l1 , mid2 = l2 , mid = (L + R) >> 1 ;
while (mid1 < r1 && tmp1[mid1 + 1].id <= mid) ++ mid1 ;
while (mid2 < r2 && tmp2[mid2 + 1].id <= mid) ++ mid2 ;
solve(l1 , mid1 , l2 , mid2 , L , mid) ; solve(mid1 , r1 , mid2 , r2 , mid , R) ;
stable_sort(tmp1 + l1 + 1 , tmp1 + mid1 + 1 , [](Node1 a , Node1 b) {
return a.Pre < b.Pre ;
}) ;
stable_sort(tmp2 + mid2 + 1 , tmp2 + r2 + 1 , [](Node2 a , Node2 b) {
return a.l < b.l ;
}) ;
int top = l1 + 1 ;
for (int i = mid2 + 1 ; i <= r2 ; ++ i) {
while (top <= mid1 && tmp1[top].Pre < tmp2[i].l) {
tree.add(tmp1[top].pos , tmp1[top].val) ; ++ top ;
}
Ans[tmp2[i].ider] += tree.Query(tmp2[i].r) - tree.Query(tmp2[i].l - 1) ;
}
for (int i = l1 + 1 ; i <= mid1 ; ++ i) {
tree.Deleted(tmp1[i].pos) ;
}
}
signed main() {
#ifdef LOCAL
freopen("1.in" , "r" , stdin) ;
freopen("1.out" , "w" , stdout) ;
#endif
n = read() , m = read() ;
for (int i = 1 ; i <= n ; ++ i) {
a[i] = read() , pi[++ cnt] = a[i] ;
}
for (int i = 1 ; i <= m ; ++ i) {
b[i].opt = read() , b[i].l = read() , b[i].r = read() ;
if (b[i].opt == 1) b[i].x = read() , pi[++ cnt] = b[i].x ;
else {
++ num ; c[num].id = i , c[num].l = b[i].l , c[num].r = b[i].r ;
}
}
stable_sort(pi + 1 , pi + cnt + 1) ; cnt = unique(pi + 1 , pi + cnt + 1) - pi - 1 ;
maxn = cnt ;
for (int i = 1 ; i <= n ; ++ i) {
a[i] = lower_bound(pi + 1 , pi + cnt + 1 , a[i]) - pi ;
Pre[i] = front[a[i]] ; front[a[i]] = i ; nPre[i] = make_pair(Pre[i] , i) ;
t[a[i]].insert(node(i , i)) ;
s.insert(Node(i , i , a[i])) ;
}
stable_sort(nPre + 1 , nPre + n + 1 , [](pair <int , int> a , pair <int , int> b) {
return a.first < b.first ;
}) ;
stable_sort(c + 1 , c + num + 1 , [](PAIRAIR a , PAIRAIR b) {
return a.l < b.l ;
}) ;
int head = 1 ;
for (int i = 1 ; i <= num ; ++ i) {
while (head <= n && nPre[head].first < c[i].l) {
tree.add(nPre[head].second , 1) , ++ head ;
}
Ans[c[i].id] += tree.Query(c[i].r) - tree.Query(c[i].l - 1) ;
} tree.clear() ;
for (int i = 1 ; i <= m ; ++ i) {
if (b[i].opt == 1) {
b[i].x = lower_bound(pi + 1 , pi + cnt + 1 , b[i].x) - pi ;
Assign(b[i].l , b[i].r , b[i].x , i) ;
} else {
++ tot2 ; tmp2[tot2].ider = i , tmp2[tot2].l = b[i].l , tmp2[tot2].r = b[i].r ;
tmp2[tot2].id = ++ total ;
}
}
solve(0 , tot1 , 0 , tot2 , 0 , total) ;
for (int i = 1 ; i <= m ; ++ i) {
if (b[i].opt == 2) {
write(Ans[i]) ; puts("") ;
}
}
}
Ynoi2016镜中的昆虫的更多相关文章
- [Ynoi2016]镜中的昆虫
题目大意: 给定一个序列,有2个操作: 1. 区间覆盖.2. 区间数颜色. 解题思路: 珂朵莉树+树套树. 看到区间覆盖当然想到珂朵莉树然而这是Ynoi 所以我们得优化掉珂朵莉树那个暴力过程. 考虑对 ...
- bzoj4941: [Ynoi2016]镜子里的昆虫
维护每个位置x的上一个相等的位置pv[x],可以把询问表示成l<=x<=r,pv[x]<l的形式,对一次修改,均摊改变O(1)个pv的取值,因此可以用平衡树预处理出pv的变化,用cd ...
- 2021record
2021-10-14 P2577 [ZJOI2004]午餐 2021-10-13 CF815C Karen and Supermarket(小小紫题,可笑可笑) P6748 『MdOI R3』Fall ...
- Codeforces 453E - Little Pony and Lord Tirek(二维线段树+ODT)
Codeforces 题目传送门 & 洛谷题目传送门 一道难度 *3100 的 DS,而且被我自己搞出来了! 不过我终究还是技不如人,因为这是一个 \(n\log^2n\) + 大常数的辣鸡做 ...
- ZJOI2019Round#1
考的这么差二试基本不用去了 不想说什么了.就把这几天听课乱记的东西丢上来吧 这里是二试乱听课笔记ZJOI2019Round#2 ZJOI Round#1 Day1 M.<具体数学>选讲 罗 ...
- 2.lvm动态逻辑卷
Lvm动态逻辑卷 一. 基本概念 LVM 把实际的物理磁盘数据映射到一个简单而灵活的虚拟逻辑存储视图上,藉以控制磁盘资源: 也就是重新考虑了管理文件系统和卷的方法,在文件系统管 ...
- VR定制开发、AR定制开发(长年承接虚拟现实、增强现实应用、VR游戏定制开发,北京公司,可签合同)
Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...
- 承接Unity3D外包公司 — 技术分享
Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...
- 阿里前CEO卫哲用自己10余年经历,倾诉B2B的三差、四率、两大坑
今日(12 月 28 日),嘉御基金创始人.阿里巴巴(B2B)前 CEO 卫哲在第三届中国 B2B 电子商务大会上进行了"B2B 冬天里的春天"的主题分享.他提出中国 B2B 行业 ...
- 合金装备V 幻痛 制作技术特辑
合金装备V:幻痛 制作特辑 资料原文出自日版CGWORLD2015年10月号 在[合金装备4(Metal Gear Solid IV)]7年后,序章作品[合金装备5 :原爆点 (Metal Gea ...
随机推荐
- Qt 之 emit、signals、slot的使用
背景 ref : https://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/index.html 信号和槽机制是 QT ...
- 实测952Mbps!四路千兆网PCIe拓展方案,国产工业级!
测试环境说明 运行系统:Debian-11.8 评估板:TL3588-EVM(RK3588J) 模块:PCIe扩展2/4路千兆网口模块 方案:无锡沐创N500L-AM2C-DD.N500L-AM4C- ...
- CANoe创建仿真工程
在现代汽车工业中,车载测试是确保车辆电子系统可靠性和功能性的关键环节.而使用CANoe创建仿真工程,不仅能够模拟真实的车辆环境,还能大大提升测试效率和准确性.那么,CANoe是如何实现这些的呢? 车载 ...
- python3 中的装饰器总结
前言 python3 中有很多好用的装饰器,本编文章作为个人笔记使用,随时更新. 正文 1. @dataclass 内容来源:chatgpt3.5 @dataclass是一个装饰器,用于给类提供自动生 ...
- 屏幕分辨率基础概念PX,PT,DP,DPR,DPI说明
屏幕分辨率基础概念说明 缩写 全称 说明 PX Device Pixels 设备像素,指设备的物理像素 PX CSS Pixels CSS像素,指CSS样式代码中使用的逻辑像素 DOT Dot 点,屏 ...
- 解决方案 | pywintypes.com_error: (-2147418111, '被呼叫方拒绝接收呼叫。', None, None)
解决方案:加一个time.sleep(1)即可
- .NET开源、简单、实用的数据库文档生成工具
前言 今天大姚给大家分享一款.NET开源(MIT License).免费.简单.实用的数据库文档(字典)生成工具,该工具支持CHM.Word.Excel.PDF.Html.XML.Markdown等多 ...
- abc--cf训练日常总结
ABC 最近遇到好多思维和位运算的题目不会做,特地过来总结一些小小的知识点. 思维题目 https://atcoder.jp/contests/abc353/tasks/abc353_c 这道题目要求 ...
- Docker PHP容器安装composer
1.进入php容器docker exec -it cb6c1fe83bff(php容器ID) bash2.安装composerphp -r "copy('https://install.ph ...
- java web 开发框架编
学习web 框架上开发需要的是安装 mysql 8.0 idea 2022 git 2.2.23 node 16以上 (新版本不好拉有些库了)jdk 最好是17以上 jdk8也是行的,反正不管 ...