KDOI-3还原数据题解
「KDOI-03」还原数据
题目描述
小 E 正在做一道经典题:
给定一个长度为 \(n\) 的序列 \(a\) 和 \(q\) 个操作,操作共有 \(2\) 种类型:
- \(\tt{1~l~r~x}\):对于所有 \(l\le i\le r\),\(a_i\leftarrow a_i+x\)。
- \(\tt{2~l~r~x}\):对于所有 \(l\le i\le r\),\(a_i\leftarrow \max(a_i,x)\)。
题目要求输出所有操作结束后的最终序列 \(a'\)。
小 E 迅速写了一份代码提交,但是发现,由于宇宙射线的影响,输入数据出现了一些小问题。具体地,对于所有 \(2\) 操作,操作中给出的 \(x\) 均被丢失了,也就是说,输入数据中的 \(2\) 操作只剩下了 \(\tt{2~l~r}\)。输出数据则没有问题。小 E 现在想要通过剩余的数据恢复原来的输入数据,请你帮助他完成这个任务。
当然,可能会有多种合法的输入数据,你需要找到其中任意一种。数据保证有解。
输入格式
从标准输入读入数据。
本题有多组测试数据。
第一行一个正整数 \(T\),表示数据组数。
对于每组测试数据,第一行两个非负整数 \(n,q\)。
第二行 \(n\) 个整数,表示初始序列 \(a_1,a_2,\ldots,a_n\)。
接下来 \(q\) 行,每行一次操作,形如 \(\tt{1~l~r~x}\) 或 \(\tt{2~l~r}\)。
接下来一行 \(n\) 个整数,表示最终序列 \(a_1',a_2',\ldots,a_n'\)。
输出格式
输出到标准输出。
本题开启自定义校验器(Special Judge)。
对于每组测试数据,设共有 \(q_2\) 个 \(2\) 操作,输出一行 \(q_2\) 个整数,第 \(i\) 个整数表示第 \(i\) 次 \(2\) 操作中所给出的 \(x\) 的值。
你需要保证 \(-10^{15}\le x\le 10^{15}\)。
样例 #1
样例输入 #1
1
5 3
1 2 3 4 5
2 3 5
1 3 4 2
2 1 1
20 2 5 6 5
样例输出 #1
3 20
提示
【样例 1 解释】
所有合法输出需要满足:第 \(1\) 个数 \(\le3\),第 \(2\) 个数恰好为 \(20\)。
【样例 2】
见选手文件中的 restore/restore2.in
与 restore/restore2.ans
。
【样例 3】
见选手文件中的 restore/restore3.in
与 restore/restore3.ans
。
【数据范围】
记 \(q_2\) 为单组数据内 \(2\) 操作的个数,\(\sum n\) 为单个测试点内所有 \(n\) 的和,\(\sum q\) 为单个测试点内所有 \(q\) 的和。
对于 \(20\%\) 的数据,保证 \(n,q\le50\),\(\sum n,\sum q\le1~000\)。
对于 \(40\%\) 的数据,保证 \(n,q\le1~000\),\(\sum n,\sum q\le10^5\)。
对于另外 \(20\%\) 的数据,保证 \(l=1,r=n\)。
对于另外 \(20\%\) 的数据,保证 \(q_2\le100\)。
对于 \(100\%\) 的数据,保证 \(1\le T\le 100\),\(1\le n,q\le 10^5\),\(1\le\sum n,\sum q\le 3\times10^5\),\(-10^9\le a_i,x\le 10^9\),\(-10^{15}\le a_i'\le10^{15}\), \(q_2\ge1\)。
【校验器】
本题样例文件较大,无法在附件中下载,请在选手文件中查看。
为了方便测试,在 \(\texttt{restore}\) 目录下我们下发了 \(\texttt{checker.cpp}\) 文件。你可以编译该文件,并使用它校验自己的输出文件。请注意它与最终评测时所用的校验器并不完全一致,你不需要也不应该关心其代码的具体内容。
编译命令为:
g++ checker.cpp -o checker -std=c++14
使用方式为:
./checker <inputfile> <outputfile> <answerfile>
校验器可能会返回以下状态中的其中一种:
- \(\tt{Accepted}\):表示你的输出完全正确。
- \(\tt{Wrong~answer~at~testcase~ x}\):表示你的输出在第 \(x\) 个测试数据出错。
【提示】
本题输入输出量较大,推荐使用较快的输入输出方式。
KDOI 出题组温馨提示:多测不清空,爆零两行泪。
题解
首先因为\(max\)操作无法被后面的抵消,倒序考虑操作,区间加改为减
引理:对于一次max操作,这个值\(k\)一定小于等于区间\([l,r]\)的最小值
证明,反证法,如果不是这样,那么取一个更大的数字,区间的最小值在经过了后面的操作之后会大于最终值
那么因为数据保证有解,我们就可以取等号,那么问题显而易见,我们开一棵维护区间最小值和区间加的线段树,倒序对于区间加改为减,每次max操作输出区间最小值即可
//fc D:\编程\C++\restore3.ans D:\编程\C++\restore3.out
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 505500
//#define int long long
#define ll long long
struct node {
int l, r;ll mn, add;
}t[N << 2];
struct ask {
int id, l, r;ll x;
}a[N];
#define lc x<<1
#define rc x<<1|1
int s[N], n, m, q, tot;
ll ans[N];
void build(int l,int r,int x) {
int mid = l + r >> 1;
t[x] = { l,r,0x3f3f3f3f3f3f3f3f,0 };
if (l == r) {
scanf("%lld", &t[x].mn);
return;
}
build(l, mid, lc);
build(mid + 1, r, rc);
t[x].mn = min(t[lc].mn, t[rc].mn);
}
inline void pushup(int x) {
t[x].mn = min(t[lc].mn, t[rc].mn);
}
inline void pushdown(node& a, node& b, node& c) {
b.add += a.add, c.add += a.add;
b.mn += a.add, c.mn += a.add;
a.add = 0;
}
inline void pushdown(int x) {
pushdown(t[x], t[lc], t[rc]);
}
void change(int l, int r, int x, ll k) {
if (l <= t[x].l && t[x].r <= r) {
t[x].add += k;
t[x].mn += k;
return;
}
pushdown(x);
int mid = t[x].l + t[x].r >> 1;
if (l <= mid)change(l, r, lc, k);
if (mid < r)change(l, r, rc, k);
pushup(x);
}
ll find(int l, int r, int x) {
if (l <= t[x].l && t[x].r <= r) {
return t[x].mn;
}
pushdown(x);
ll ans = 0x3f3f3f3f3f3f3f3f;
int mid = t[x].l + t[x].r >> 1;
if (l <= mid)ans = min(ans, find(l, r, lc));
if (mid < r)ans = min(ans, find(l, r, rc));
pushup(x);
return ans;
}
void init() {
tot = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", &s[i]);
for (int i = 1; i <= m; i++) {
int op, l, r;ll k;
scanf("%d%d%d", &op, &l, &r);
if (op == 1) {
scanf("%lld", &k);
a[i] = { op,l,r,k };
continue;
}
a[i] = { op,l,r,-1ll };
}
build(1, n, 1);
for (int i = m; i > 0; i--) {
if (a[i].id == 1) {
change(a[i].l, a[i].r, 1, -a[i].x);
continue;
}
ans[++tot] = find(a[i].l, a[i].r, 1);
}
for (int i = tot; i; i--) {
printf("%lld ", ans[i]);
}
puts("");
}
signed main() {
// freopen("restore2.in","r",stdin);
// freopen("restore2.out","w",stdout);
int t;
scanf("%d", &t);
while (t--) {
init();
}
return 0;
}