[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\) 最近的位置。

那其实考虑一个简单的结论:

\[ans = \sum_{l \le i \le r}[Pre_i < l]
\]

其实啥意思呢,就是说他前面的数必须在 \(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镜中的昆虫的更多相关文章

  1. [Ynoi2016]镜中的昆虫

    题目大意: 给定一个序列,有2个操作: 1. 区间覆盖.2. 区间数颜色. 解题思路: 珂朵莉树+树套树. 看到区间覆盖当然想到珂朵莉树然而这是Ynoi 所以我们得优化掉珂朵莉树那个暴力过程. 考虑对 ...

  2. bzoj4941: [Ynoi2016]镜子里的昆虫

    维护每个位置x的上一个相等的位置pv[x],可以把询问表示成l<=x<=r,pv[x]<l的形式,对一次修改,均摊改变O(1)个pv的取值,因此可以用平衡树预处理出pv的变化,用cd ...

  3. 2021record

    2021-10-14 P2577 [ZJOI2004]午餐 2021-10-13 CF815C Karen and Supermarket(小小紫题,可笑可笑) P6748 『MdOI R3』Fall ...

  4. Codeforces 453E - Little Pony and Lord Tirek(二维线段树+ODT)

    Codeforces 题目传送门 & 洛谷题目传送门 一道难度 *3100 的 DS,而且被我自己搞出来了! 不过我终究还是技不如人,因为这是一个 \(n\log^2n\) + 大常数的辣鸡做 ...

  5. ZJOI2019Round#1

    考的这么差二试基本不用去了 不想说什么了.就把这几天听课乱记的东西丢上来吧 这里是二试乱听课笔记ZJOI2019Round#2 ZJOI Round#1 Day1 M.<具体数学>选讲 罗 ...

  6. 2.lvm动态逻辑卷

    Lvm动态逻辑卷 一.             基本概念 LVM 把实际的物理磁盘数据映射到一个简单而灵活的虚拟逻辑存储视图上,藉以控制磁盘资源: 也就是重新考虑了管理文件系统和卷的方法,在文件系统管 ...

  7. VR定制开发、AR定制开发(长年承接虚拟现实、增强现实应用、VR游戏定制开发,北京公司,可签合同)

    Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...

  8. 承接Unity3D外包公司 — 技术分享

    Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...

  9. 阿里前CEO卫哲用自己10余年经历,倾诉B2B的三差、四率、两大坑

    今日(12 月 28 日),嘉御基金创始人.阿里巴巴(B2B)前 CEO 卫哲在第三届中国 B2B 电子商务大会上进行了"B2B 冬天里的春天"的主题分享.他提出中国 B2B 行业 ...

  10. 合金装备V 幻痛 制作技术特辑

    合金装备V:幻痛 制作特辑 资料原文出自日版CGWORLD2015年10月号   在[合金装备4(Metal Gear Solid IV)]7年后,序章作品[合金装备5 :原爆点 (Metal Gea ...

随机推荐

  1. 处理 3d 视频的简单理论基础

    背景 公司产品需要满足一些带有3d功能的应用场景,需要需要懂得如何处理3d信号.之前在调试以前产品的时候,发现处理3d信号的时候,是由2个画面叠加起来的. 导言 3D视频(或3D信号)为什么是两个画面 ...

  2. k8s学习总结之各组件功能及作用的详细介绍

    K8S集群架构: 一个K8S集群是由两大部分组成: Master节点和Node节点 一.Master节点主要包括API Server.Scheduler.Controller manager.etcd ...

  3. 嵌入式ARM端测试手册——全志T3+Logos FPGA开发板(上)

    前 言 本指导文档适用开发环境: Windows开发环境:Windows 7 64bit.Windows 10 64bit Linux开发环境:Ubuntu18.04.4 64bit 虚拟机:VMwa ...

  4. spark共享变量---广播变量和累加变量

    从三个方面来分析:1.什么时候使用,2.原理是什么3.性能上有什么优化 累加变量:--(自定义累加器很重要) 使用场景:累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数,如:统计日志中空行 ...

  5. 【规范】Git分支管理,看看我司是咋整的

    前言 缘由 Git分支管理好,走到哪里都是宝 事情起因: 最近翻看博客中小伙伴评论时,发现文章[规范]看看人家Git提交描述,那叫一个规矩一条回复: 本狗亲测在我司中使用规范的好处,遂把我司的Git分 ...

  6. 拟合算法与matlab工具包实现

    与插值问题不同,在拟合问题中不需要曲线一定经过给定的点.拟合问题的目标是寻求一个函数(曲线),使得该曲线在某种准则下与所有的数据点最为接近,即曲线拟合的最好(最小化损失函数) 目录 一.插值和拟合的区 ...

  7. 轻松掌握useAsyncData获取异步数据

    title: 轻松掌握useAsyncData获取异步数据 date: 2024/7/12 updated: 2024/7/12 author: cmdragon excerpt: 摘要:本文详细介绍 ...

  8. 【实操记录】MySQL主从配置

    本文使用MySQL原生支持的主从同步机制,详细记录了配置步骤及运维操作方法,可供大家直接参考.使用. 本文假设已经部署了两台主机的MySQL软件,且数据库服务正常,详细部署步骤可本站搜索:" ...

  9. IntersectionObserver 实现图片懒加载

    背景 最近使用express做导航类型网站,因为这个是后端jade渲染,浏览器拿到页面之后,解析出来dom结构,导致100+的图片瞬间加载,严重浪费了宽带资源,加重服务器负担,因此打算延迟加载图片 模 ...

  10. 如何在 Vue 和 JavaScript 中截取视频任意帧图片

    如何在 Vue 和 JavaScript 中截取视频任意帧图片 大家好!今天我们来聊聊如何在 Vue 和 JavaScript 中截取视频的任意一帧图片.这个功能在很多场景下都非常有用,比如视频编辑. ...