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("") ;
}
}
}