A
B

解题报告 P4690 [Ynoi Easy Round 2016] 镜中的昆虫

题意描述:维护一个长为 \(n\) 的序列,支持区间查询颜色数和区间推平操作。

\(n、m \le 1 \times 10 ^ 5,a_i \le 1 \times 10 ^ 9\)

时限:1s。

空间:64MB。

首先我们考虑一下这个题的简单版本。

维护一个长为 \(n\) 的序列,支持区间查询颜色数和单点修改操作(P1903 [国家集训队] 数颜色 / 维护队列 /【模板】带修莫队)。

对于这种数区间颜色种类问题,有一个经典结论:设序列中一个数的与其颜色相同的上一个数为 \(pre_i\),区间 \([l,r]\) 内的颜色种类数为 \(\sum_{i = l}^{r}{[pri_i < l]}\)

显然这个是正确的。

然后我们发现每次查询操作都相当于求 \(\sum_{i = 1}^{r}{[pri_i < l]} - \sum_{i = l - 1}^{r}{[pri_i < l]}\)

对于每次修改操作,发现只需要修改至多 \(3\) 个点(这个点本身,这个点之前颜色的后继,这个点之后颜色的后继)。

因为有修改操作。所以这个相当于是一个三维偏序问题。

将每个点转化为二维数点:\((i,pri_i)\)。相当于是在查询一个二维平面内给定矩阵内点的个数。

这样直接上 cdq 或者 KD-Tree 即可解决。

点击查看 cdq 代码
#include <stdio.h>
#include <iostream>
#include <set>
#define lowbit(x) (x & (-x))
#define IT set<int>::iterator
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
#define Blue_Archive return 0
using namespace std;
constexpr int N = 1e6 + 7;

int n;
int m;
int tot;
int top;
int a[N];
int b[N];
int tr[N];
int pri[N];
int ans[N];

set<int> s[N];

struct miku
{
	int op; // 记录操作种类
	int l,r;
	int id,tye;
}q[N],tmp[N];

inline int read() // 不读负数的普通快读
{
	int k = 0,f = 1;
	int c = getchar_unlocked();
	while(c < '0' || c > '9') c = getchar_unlocked();
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + (c ^ 48),c = getchar_unlocked();
	return k * f;
}

inline void write(int x) // 普通快写
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline void ins(int pos,int val)
{
	for(int i = pos;i <= n;i += lowbit(i)) tr[i] += val;
}

inline int query(int pos)
{
	int res = 0;
	for(int i = pos;i;i -= lowbit(i)) res += tr[i];
	return res;
}

inline void cdq(int l,int r)
{
	if(l == r) return;
	int mid = (l + r) >> 1;
	cdq(l,mid);cdq(mid + 1,r);
	int itl = l,itr = mid + 1;
	top = l - 1;
	while(itl <= mid && itr <= r)
	{
		if(q[itl].r < q[itr].r)
		{
			if(!q[itl].op) ins(q[itl].l,q[itl].tye);
			tmp[++ top] = q[itl ++];
		}
		else 
		{
			if(q[itr].op) ans[q[itr].id] += q[itr].tye * query(q[itr].l);
			tmp[++ top] = q[itr ++];
		}
	}
	while(itl <= mid)
	{
		if(!q[itl].op) ins(q[itl].l,q[itl].tye);
		tmp[++ top] = q[itl ++];
	}
	while(itr <= r)
	{
		if(q[itr].op) ans[q[itr].id] += q[itr].tye * query(q[itr].l);
		tmp[++ top] = q[itr ++];
	}
	for(int i = l;i <= mid;i ++) if(!q[i].op) ins(q[i].l,-q[i].tye);
	for(int i = l;i <= r;i ++) q[i] = tmp[i];
}

inline int pre(int x,int y)
{
	IT it = s[x].lower_bound(y);
	return (it == s[x].begin()) ? 0 : *(-- it);
}

inline int nxt(int x,int y)
{
	IT it = s[x].upper_bound(y);
	return (it == s[x].end()) ? 0 : *(it);
}

signed main()
{
	#ifndef ONLINE_JUDGE
		freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	#endif
	n = read();
	m = read();
	for(int i = 1;i <= n;i ++) 
	{
		a[i] = read();
		s[a[i]].insert(i);
		q[++ tot] = {0,i,pre(a[i],i),0,1};
	}
	char op;
	int cnt = 0;
	for(int i = 1,l,r,opt;i <= m;i ++)
	{
		cin >> op;
		l = read();
		r = read();
		if(op == 'Q')
		{
			cnt ++;
			q[++ tot] = {1,r,l,cnt,1};
			q[++ tot] = {1,l - 1,l,cnt,-1};
		}
		else 
		{
			q[++ tot] = {0,l,pre(a[l],l),0,-1};
			q[++ tot] = {0,l,pre(r,l),0,1};
			opt = nxt(a[l],l);
			if(opt)
			{
				q[++ tot] = {0,opt,pre(a[opt],opt),0,-1};
				q[++ tot] = {0,opt,pre(a[l],l),0,1};
			}
			opt = nxt(r,l - 1);
			if(opt)
			{
				q[++ tot] = {0,opt,pre(a[opt],opt),0,-1};
				q[++ tot] = {0,opt,l,0,1};
			}
			s[a[l]].erase(l);
			a[l] = r;
			s[a[l]].insert(l);
		}
	}
	cdq(1,tot);
	for(int i = 1;i <= cnt;i ++) write(ans[i]),ent;
	Blue_Archive;
}

然后我们回到原问题。

我们惊奇地发现,如果区间修改可以是单点修改就好了。

我们需要一种可以支持区间覆盖的数据结构(且常熟小 && 复杂度小于 \(log_2n\))。

ynoi 的题当然是用珂朵莉来解决啦 ~\ (≧▽≦) /~

于是我们考虑用珂朵莉树来维护。

为了保证时间复杂度,考虑将一段颜色缩成一个点。

这样子修改操作的时间复杂度将不会超过 \(O(n + m)\)

证明:

我们每次修改操作最多加入 3 个点,总共加入 \(O(m)\) 级别的点。

删除操作最多将点全部删除,所以时间复杂度为 \(O(n + m)\) 级别的点。

知道这个结论了,我们就可以直接套上边的 cdq 板子,然后简单地切掉这道黑了。

QvQ

点击查看代码(不卡常版本)
#include <unordered_map>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <set>
#define lowbit(x) (x & (-x))
#define IT set<Chtholly>::iterator
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
#define Blue_Archive return 0
using namespace std;
constexpr int N = 1e5 + 7;

int n;
int m;
int tot;
int top;
int cnt;
int a[N];
int b[N];
int tr[N];
int pri[N];
int las[N];
int ans[N];

struct miku
{
	bool op; // 记录操作种类
	int l,r;
	int id,tye;
}q[N * 11],tmp[N * 11];

struct Chtholly
{
	signed l,r;
	mutable int val;
	Chtholly(int L,int R = -1,int V = 0) : l(L),r(R),val(V) {}
	friend bool operator < (Chtholly a,Chtholly b){return a.l < b.l;}
};

set<Chtholly> s;
set<Chtholly> col[N << 1];

unordered_map<int,int> mp;

inline int read() // 不读负数的普通快读
{
	int k = 0,f = 1;
	int c = getchar_unlocked();
	while(c < '0' || c > '9') c = getchar_unlocked();
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + (c ^ 48),c = getchar_unlocked();
	return k * f;
}

inline void write(int x) // 普通快写
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline void del(int l,int r,int val)
{
	col[val].erase(Chtholly(l,r,val));
	s.erase(Chtholly(l,r,val));
}

inline IT ins(int l,int r,int val)
{
	col[val].insert(Chtholly(l,r,val));
	return s.insert(Chtholly(l,r,val)).first;
}

inline IT split(signed pos)
{
	IT it = s.lower_bound(Chtholly(pos));
	if(it != s.end() && it -> l == pos) return it;
	it --;
	int l = it -> l,r = it -> r,val = it -> val;
	del(l,r,val);
	ins(l,pos - 1,val);
	return ins(pos,r,val);
}

inline int pre(signed pos) // 找到位置为 pos 的颜色的前驱
{
	IT it = s.upper_bound(Chtholly(pos,0,0));
	it --;
	if(it -> l < pos) return pos - 1;
	IT op = col[it -> val].lower_bound(Chtholly(pos,0,0));
	if(op != col[it -> val].begin()) return (-- op) -> r;
	return 0;
}

inline void assign(int l,int r,int val,int id) // 插入颜色段覆盖
{
	IT it2 = split(r + 1),it1 = split(l);
	vector<int> tmp;
	for(IT it = it1;it != it2;it ++)
	{
		if(it != it1) tmp.emplace_back(it -> l);
		IT nxt = col[it -> val].upper_bound(*it);
		if(nxt != col[it -> val].end()) tmp.emplace_back(nxt -> l);
		col[it -> val].erase(*it);
	}
	s.erase(it1,it2);
	s.insert(Chtholly(l,r,val));
	col[val].insert(Chtholly(l,r,val));
	tmp.emplace_back(l);
	IT nxt = col[val].upper_bound(Chtholly(l,r,val));
	if(nxt != col[val].end()) tmp.emplace_back(nxt -> l);
	for(int v : tmp)
	{
		if(v < 0 || v > n) continue;
		q[++ cnt] = miku{0,v,pri[v],id,-1};
		pri[v] = pre(v);
		q[++ cnt] = miku{0,v,pri[v],id,1};
		if(pri[v] > N) cerr << pri[v] << '\n';
	}
}

inline void ins(int pos,int val)
{
	for(int i = pos;i <= n;i += lowbit(i)) tr[i] += val;
}

inline int query(int pos)
{
	int res = 0;
	for(int i = pos;i;i -= lowbit(i)) res += tr[i];
	return res;
}

inline void clear(int pos)
{
	for(int i = pos;i <= n;i += lowbit(i)) tr[i] = 0;
}

inline void cdq(int l,int r)
{
	if(l == r) return;
	int mid = (l + r) >> 1;
	cdq(l,mid);cdq(mid + 1,r);
	int itl = l,itr = mid + 1;
	top = l - 1;
	while(itl <= mid && itr <= r)
	{
		if(q[itl].l <= q[itr].l)
		{
			if(!q[itl].op) ins(q[itl].r,q[itl].tye);
			tmp[++ top] = q[itl ++];
		}
		else 
		{
			if(q[itr].op) ans[q[itr].id] += q[itr].tye * query(q[itr].r);
			tmp[++ top] = q[itr ++];
		}
	}
	while(itr <= r)
	{
		if(q[itr].op) ans[q[itr].id] += q[itr].tye * query(q[itr].r);
		tmp[++ top] = q[itr ++];
	}
	for(int i = l;i < itl;i ++) if(!q[i].op) clear(q[i].r);
	while(itl <= mid) tmp[++ top] = q[itl ++];
	for(int i = l;i <= r;i ++) q[i] = tmp[i];
}

signed main()
{
	#ifndef ONLINE_JUDGE
		freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	#endif
	n = read();
	m = read();
	for(int i = 1;i <= n;i ++) 
	{
		a[i] = read();
		if(!mp[a[i]]) mp[a[i]] = ++ tot;
		a[i] = mp[a[i]];
		pri[i] = las[a[i]];
		q[++ cnt] = {0,i,pri[i],0,1};
		las[a[i]] = i;
		s.insert(Chtholly(i,i,a[i]));
		col[a[i]].insert(Chtholly(i,i,a[i]));
	}
	for(int i = 1,l,r,op,v;i <= m;i ++)
	{
		op = read();
		if(op == 1)
		{
			l = read();
			r = read();
			v = read();
			if(!mp[v]) mp[v] = ++ tot;
			v = mp[v];
			assign(l,r,v,i);
		}
		else 
		{
			l = read();
			r = read();
			q[++ cnt] = {1,r,l - 1,i,1};
			q[++ cnt] = {1,l - 1,l - 1,i,-1};
		}
	}
	for(int i = 1;i <= cnt;i ++) q[i].r ++;
	cdq(1,cnt);
	for(int i = 1;i <= m;i ++) if(ans[i]) write(ans[i]),ent;
	Blue_Archive;
}
posted @ 2025-11-11 20:36  MyShiroko  阅读(33)  评论(1)    收藏  举报