D - Array Restoration
题意:一个n个元素的数组,恰好进行q次修改,第i次修改可以把连续的一段区间赋值为i。求是否可以构造出题目提供的数组,0表示可以填任意值。
题解:
先解决没有0的情况的问题。每次都是区间更新一次,那么查询到最左的i和最右的i(假如存在),那么中间部分的最小值必须>=i,这个看起来就像线段树查询,实际上用st表也可以。当所有的查询都满足并且最大值q存在,那么合法(中间缺的值全都由最大值覆盖掉)。
有0的情况稍微复杂一些,首先假如最大值q不存在,那么随便赋值任意一个0变成最大值就可以了。如果中间的段有0的话,就把这些0强制赋值为i,注意到这样子不会使区间的最小值变大,也就是这样构造是最优的,既使得lr之间连续,也满足无后效性。假如还有0剩余就把0赋值为左右相同的值,但是假如左右也是0怎么办呢?不妨把a[0]设为1,然后每个0的值都从其左边继承。
实现的时候就是一棵最小值线段树和一棵区间更新线段树。最后把标记全部下传给叶子。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 200000;
const int INF = 0x3f3f3f3f;
int a[MAXN + 5];
struct SegmentTree1 {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
static const int INF = 0x3f3f3f3f;
int mi[(MAXN << 2) + 5];
void PushUp(int o) {
mi[o] = min(mi[ls], mi[rs]);
}
void Build(int o, int l, int r) {
if(l == r) {
mi[o] = (a[l] == 0 ? INF : a[l]);
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
}
int QueryMin(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return mi[o];
} else {
int m = l + r >> 1;
int res = INF;
if(ql <= m)
res = QueryMin(ls, l, m, ql, qr);
if(qr >= m + 1)
res = min(res, QueryMin(rs, m + 1, r, ql, qr));
return res;
}
}
#undef ls
#undef rs
} st1;
struct SegmentTree2 {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 200000;
static const int INF = 0x3f3f3f3f;
int a[MAXN + 5], lz[(MAXN << 2) + 5];
void PushDown(int o, int l, int r) {
if(lz[o]) {
lz[ls] = lz[o];
lz[rs] = lz[o];
lz[o] = 0;
}
}
void Build(int o, int l, int r) {
if(l == r) {
a[l] = 0;
} else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
}
lz[o] = 0;
}
void Update(int o, int l, int r, int ql, int qr, int v) {
if(ql <= l && r <= qr) {
lz[o] = v;
} else {
PushDown(o, l, r);
int m = l + r >> 1;
if(ql <= m)
Update(ls, l, m, ql, qr, v);
if(qr >= m + 1)
Update(rs, m + 1, r, ql, qr, v);
}
}
void PushDownAll(int o, int l, int r) {
if(l == r) {
a[l] = lz[o];
} else {
PushDown(o, l, r);
int m = l + r >> 1;
PushDownAll(ls, l, m);
PushDownAll(rs, m + 1, r);
}
}
#undef ls
#undef rs
} st2;
int n, q, maxai, lm[MAXN + 5], rm[MAXN + 5];
void solve0() {
if(maxai != q) {
puts("NO");
return;
}
st1.Build(1, 1, n);
for(int i = 1; i <= q; ++i) {
if(lm[i] == 0)
continue;
if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
puts("NO");
return;
}
}
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], " \n"[i == n]);
}
void solve1() {
st1.Build(1, 1, n);
for(int i = 1; i <= q; ++i) {
if(lm[i] == 0)
continue;
if(st1.QueryMin(1, 1, n, lm[i], rm[i]) < i) {
puts("NO");
return;
}
}
if(lm[q] == 0)
a[lm[0]] = q;
st2.PushDownAll(1, 1, n);
a[0] = 1;
for(int i = 1; i <= n; ++i) {
if(a[i] == 0)
a[i] = st2.a[i];
if(a[i] == 0)
a[i] = a[i - 1];
}
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], " \n"[i == n]);
}
void test_case() {
scanf("%d%d", &n, &q);
maxai = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if(a[i] > maxai)
maxai = a[i];
if(lm[a[i]] == 0)
lm[a[i]] = i;
rm[a[i]] = i;
}
if(lm[0] == 0)
solve0();
else
solve1();
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
//scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
/*
1. 小数据问题退化:
输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
2. 内存:
内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
多组数据有进行初始化吗?memset会不会翻车?
3. 算术溢出:
乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
4. 习惯:
函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
5. 其他bug:
基环树是树加环,不是链加环。
*/