宝石专家 题解 +小结
1.前言
我是
s
b
sb
sb ,
T
4
T4
T4 我最开始写的是分块,卡了半天常数,浪费了
1.5
h
+
1.5h^+
1.5h+,最后花了
30
m
i
n
30min
30min 写
C
D
Q
CDQ
CDQ。真·暴力比正解复杂 。所以之后还是不要想卡评测机这件事,卡常骗分又不能增强实力,又浪费时间。(分块的被
h
a
c
k
hack
hack 了, 氧化钙
2.题解
C
D
Q
CDQ
CDQ
将所有相邻的特征值相同的宝石全部找出来,记作一个三元组(为了用三维偏序)
(
i
d
x
i
,
l
i
,
r
i
)
(idx_i,l_i, r_i)
(idxi,li,ri)(后面就称为修改区间)。
i
d
x
idx
idx表示时间戳(即操作顺序),
l
i
l_i
li,
r
i
r_i
ri 表示相邻的特征值相同的宝石的下表标
我们的询问 [ l , r ] [l, r] [l,r] 相当于寻找 m i n ( r i − l i ) ( l ≤ l i , r i ≤ r ) min (r_i-l_i)(l \leq l_i, r_i \leq r) min(ri−li)(l≤li,ri≤r),用三维偏序就行了。
按照 l l l 排序,现在 [ l , m i d ] [l, mid] [l,mid] 的指针为 i i i, [ m i d + 1 , r ] [mid + 1, r] [mid+1,r] 的指针为 j j j,则我们需要找到 m i n ( r p − l p ) ( l j ≤ l p , r p ≤ r j , p ∈ [ l , i ] ) min (r_p-l_p)(l_j \leq l_p, r_p \leq r_j, p \in [l,i]) min(rp−lp)(lj≤lp,rp≤rj,p∈[l,i]), l l l 的条件已经满足了,所以用树状数组,维护满足 r i ≤ r r_i \leq r ri≤r 的最小区间即可
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
template <typename T>
void read (T &x) {
x = 0; T f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar ();
}
x *= f;
}
template <typename T>
void write (T x) {
if (x < 0) {
x = -x;
putchar ('-');
}
if (x < 10) {
putchar (x + '0');
return;
}
write (x / 10);
putchar (x % 10 + '0');
}
template <typename T>
void print (T x, char ch) {
write (x); putchar (ch);
}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 2 * 1e5 + 10;
const int Inf = 0x3f3f3f3f;
int n, m;
int a[Maxn + 5], bak[Maxn + 5];
vector <int> v;
int rev[Maxn + 5];
int Find (int x) {
return lower_bound (v.begin (), v.end (), x) - v.begin ();
}
int len_q, len_ans;
int ans[Maxn + 5];
struct Node {
int op, l, r, val, id;
Node () {}
Node (int OP, int L, int R, int VAL, int ID) {
op = OP; l = L; r = R; val = VAL; id = ID;
}
//op = 0,表示有一个修改区间 [l, r],长度为 val (即 r - l, 但懒得打 r - l),id 无用
//op = 1,表示有一个询问区间 [l, r],val 记录答案,id 记录 ta 是第几个询问。
}q[Maxn * 2 + 5], tem[Maxn * 2 + 5];
struct Arrey {
int BIT[Maxn + 5];
void Init () {
memset (BIT, 0x3f, sizeof BIT);
}
int lowbit (int x) { return x & -x; }
void Update (int Index, int x) {
//在 Index 处增加一个点 x
for (int i = Index; i <= Maxn; i += lowbit (i))
BIT[i] = Min (BIT[i], x);
}
int Query (int Index) {
//返回 <= i 的 r 的最小值
int res = Inf;
for (int i = Index; i >= 1; i -= lowbit (i))
res = Min (res, BIT[i]);
return res;
}
void Clean (int Index) {
//清空 Index 处的修改
for (int i = Index; i <= Maxn; i += lowbit (i))
BIT[i] = Inf;
}
}Tr;
bool operator < (Node x, Node y) {
//重载运算符
if (x.l != y.l) return x.l > y.l;
else if (x.r != y.r) return x.r < y.r;
else return x.op < y.op;
}
void CDQ (int l, int r) {
//三维偏序模板
if (l == r) return;
int mid = (l + r) >> 1;
CDQ (l, mid); CDQ (mid + 1, r);
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (q[i] < q[j]) {
if (q[i].op == 0)
Tr.Update (q[i].r, q[i].val);
tem[k++] = q[i++];
}
else {
if (q[j].op == 1)
q[j].val = Min (q[j].val, Tr.Query (q[j].r));
tem[k++] = q[j++];
}
}
while (i <= mid) {
if (q[i].op == 0)
Tr.Update (q[i].r, q[i].val);
tem[k++] = q[i++];
}
while (j <= r) {
if (q[j].op == 1)
q[j].val = Min (q[j].val, Tr.Query (q[j].r));
tem[k++] = q[j++];
}
for (i = l; i <= mid; i++) {
if (q[i].op == 0)
Tr.Clean (q[i].r);
}
for (i = l; i <= r; i++) q[i] = tem[i];
}
signed main () {
freopen ("5.in", "r", stdin);
Tr.Init ();
read (n); read (m);
for (int i = 1; i <= n; i++) {
read (a[i]);
v.push_back (a[i]);
}
sort (v.begin (), v.end ());
v.erase (unique (v.begin (), v.end ()), v.end ());
for (int i = 1; i <= n; i++)
rev[i] = Find (a[i]);
for (int i = 1; i <= n; i++) {
if (bak[rev[i]] != 0)
q[++len_q] = Node (0, bak[rev[i]], i, i - bak[rev[i]], -1);
//有一个相邻相同点对 (bak[rev[i]], i)
bak[rev[i]] = i;
}
for (int i = 1; i <= m; i++) {
int l, r; read (l); read (r);
q[++len_q] = Node (1, l, r, Inf, ++len_ans);
//查询 [l, r]
}
CDQ (1, len_q);
for (int i = 1; i <= len_q; i++) {
if (q[i].op == 1)
ans[q[i].id] = q[i].val;
}
for (int i = 1; i <= m; i++) {
if (ans[i] > n) {
putchar ('-');
putchar ('1');
putchar ('\n');
}
else print (ans[i], '\n');
}
return 0;
}
3.小结
0 m i n − 5 m i n 0min-5min 0min−5min:写完 T 1 T1 T1 (AC
5 m i n − 15 m i n 5min-15min 5min−15min:想了一下 T 2 T2 T2,思路有问题,码量太大,于是去看 T 3 T3 T3(还好没上头,吸取了昨天晚上一直淦T4,结果思路错了,浪费了 2 h + 2h^+ 2h+ 的教训
15 m i n − 25 m i n 15min-25min 15min−25min:写完 T 3 T3 T3,手玩几组小数据,没有问题(AC
25 m i n − 26 m i n 25min-26min 25min−26min: T 4 T4 T4 一看就会,和小 Y 的房间做法一样,分块乱搞就行了。(但是时间复杂度超了,而且没发现
26 m i n − 50 m i n 26min-50min 26min−50min: T 4 T4 T4 写完,调过所有样例,搞了一组大数据, 8 s 8 s 8s 才跑过
50 m i n − 150 m i n ( 左 右 ) 50min-150min(左右) 50min−150min(左右), T 4 T4 T4 卡常(太浪费时间了)。
150 m i n − 160 m i n 150min-160min 150min−160min:回头看 T 2 T2 T2,想到了怎样优化代码,很快就打出来了(AC
160 m i n − 165 m i n 160min-165min 160min−165min:思考 T 4 T4 T4 & 人生
165 m i n − 205 m i n 165min-205min 165min−205min:写出 C D Q CDQ CDQ 正解,压线做完所有题目。(AC
205 m i n − 210 m i n 205min-210min 205min−210min:小黄鸭检查
之前受到了剪枝优化带师的刺激(现代豪宅暴力连虚边 70 pts),理论和实际时间复杂度的差别直接震惊我,以至于我考试时还想靠一些剪枝来水掉 qwq,这种做法是不可取的,到了最后追求高分的时候,肯定是想正解,剪枝暴力永远都不能让人达到顶尖水平。虽然距离顶尖水平还很遥远,但是开始的几步一定不能走偏,所以之后一定不能再浪费如此多时间去剪枝卡常了啊 o w o owo owo.