解题报告 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;
}

浙公网安备 33010602011771号