本文涉及知识点
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
P8902 [USACO22DEC] Range Reconstruction S
题目描述
Bessie 有一个数组 a 1 , ⋯ , a N a_1, \cdots, a_N a1,⋯,aN,其中 1 ≤ N ≤ 300 1 \le N \le 300 1≤N≤300 并对于所有 i i i 有 0 ≤ a i ≤ 10 9 0 \le a_i \le 10^9 0≤ai≤109。她不会告诉你数组 a a a 本身,但她会告诉你 a a a 的每个子数组的全距。也就是说,对于每对索引 i ≤ j i \le j i≤j,Bessie 告诉你 r i , j = max a [ i ⋯ j ] − min a [ i ⋯ j ] r_{i,j}= \max a[i \cdots j]− \min a[i \cdots j] ri,j=maxa[i⋯j]−mina[i⋯j]。给定这些 r r r 的值,构造一个可以作为 Bessie 的原始数组的数组。你的数组中的数值应在 [ − 10 9 , 10 9 ] [−10^9,10^9] [−109,109] 范围内。
输入格式
输入的第一行包含 N N N。
以下 N N N 行,第 i i i 行包含整数 r i , i , r i , i + 1 , ⋯ , r i , N r_{i,i},r_{i,i+1}, \cdots ,r_{i,N} ri,i,ri,i+1,⋯,ri,N。
输入保证存在某个数组 a a a,其中的数值在 [ 0 , 10 9 ] [0,10^9] [0,109] 范围内,满足对于所有的 i ≤ j i \le j i≤j,有 r i , j = max a [ i ⋯ j ] − min a [ i ⋯ j ] r_{i,j}= \max a[i \cdots j]−\min a[i\cdots j] ri,j=maxa[i⋯j]−mina[i⋯j]。
输出格式
输出一行,包含 N N N 个整数 b 1 , b 2 , ⋯ , b N b_1,b_2, \cdots ,b_N b1,b2,⋯,bN,在 [ − 10 9 , 10 9 ] [−10^9,10^9] [−109,109] 范围内,表示你的数组。这些数需要满足对于所有的 i ≤ j i \le j i≤j 有 r i , j = max a [ i ⋯ j ] − min a [ i ⋯ j ] r_{i,j}= \max a[i \cdots j]−\min a[i\cdots j] ri,j=maxa[i⋯j]−mina[i⋯j]。
输入输出样例 #1
输入 #1
3
0 2 2
0 1
0
输出 #1
1 3 2
输入输出样例 #2
输入 #2
3
0 1 1
0 0
0
输出 #2
0 1 1
输入输出样例 #3
输入 #3
4
0 1 2 2
0 1 1
0 1
0
输出 #3
1 2 3 2
输入输出样例 #4
输入 #4
4
0 1 1 2
0 0 2
0 2
0
输出 #4
1 2 2 0
说明/提示
样例 1 解释
例如, r 1 , 3 = max a [ 1 ⋯ 3 ] − min a [ 1 ⋯ 3 ] = 3 − 1 = 2 r_{1,3}=\max a[1 \cdots 3]−\min a[1\cdots 3]=3−1=2 r1,3=maxa[1⋯3]−mina[1⋯3]=3−1=2。
样例 2 解释
这个样例满足子任务 1 1 1 的限制。
样例 3 解释
这个样例满足子任务 2 的限制。
测试点性质
- 测试点 5 5 5 满足 r 1 , N ≤ 1 r_{1,N} \le 1 r1,N≤1。
- 测试点 6 − 8 6-8 6−8 满足对于所有 1 ≤ i < N 1 \le i<N 1≤i<N 均有 r i , i + 1 = 1 r_{i,i+1}=1 ri,i+1=1。
- 测试点 9 − 14 9-14 9−14 没有额外限制。
构造 前缀和
性质一 :数组a所有元素+x,其全距不变。故任意a各元素-a[0]后,a[0]就是0。
性质二 :数组a所有元素乘以-1,其全距不变。任意子段,最大值变成-Min,最小值变成-Max。
性质三 :令 c i = ∣ l r i − 1 , i ∣ ci = |lr_{i-1,i}| ci=∣lri−1,i∣,则 a[i] = a[i-1]+c或a[i]= a[i-1]-c。
结论一 :根据性质一和性质二,一定存在解a[0]=0,a[1]=c1。
结论二 :本题一定有解,如果 a[i-1]+c和a[i]= a[i-1]-c,只有一个不矛盾注1 。那就是解。
结论三 :如果a[i]的两个解,都成立,则任选一个。下面来证明:
如果ci是0,则是相同解,不讨论。
考虑a[i-2...i]和a[i-2...i-1]的差别。 如果 a i − 1 > a i − 2 , a i − 2 − c i 如果a_{i-1}>a_{i-2},a_{i-2}- ci 如果ai−1>ai−2,ai−2−ci最小数减少ci,最大数不变。a_{i-2}+ |ci|的最小数不变,最大数不变,或增加的幅度小于ci。 故两者的 故两者的 故两者的lr_{i-2,i} 不可能相等。同理 不可能相等。同理 不可能相等。同理a_{i-1}<a_{i-2} 也不成立。故只有 也不成立。故只有 也不成立。故只有a_{i-1}等于a_{i-2} 时,可能成立。同理 a [ i − 3 ] 、 a [ i − 4 ] 时,可能成立。 同理a[i-3]、a[i-4] 时,可能成立。同理a[i−3]、a[i−4]cdots$也要相等。即:a[0...i-1]全部相等。由于相等,将a[1...i-1]全删除,最后再加上。删除后,根据性质一和性质二,可以得出都是合法解。
注1 :即lr[i...r]都符合。
利用前缀和(极值)可以将判断的时间复杂度降到O(1),pmin[j]记录a[j...i]的最小值,pmax[j]记录a[j...i]的最大值。这样时间复杂度是:O(nn)。
第一个层循环枚举a[i],第二循环判断是否和 l r j , i lr_{j,i} lrj,i矛盾。
展望:
只需要枚举任意a[j] != a[i-1],故时间复杂度可以进一步压缩。
代码
核心代码
cpp
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
vector<T> ret;
T tmp;
while (cin >> tmp) {
ret.emplace_back(tmp);
if ('\n' == cin.get()) { break; }
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = -x;
if (!x)
*m_p++ = 48;
while (x)
num[++sp] = x % 10, x /= 10;
while (sp)
*m_p++ = num[sp--] + 48;
AuotToFile();
}
void writestr(const char* sz) {
strcpy(m_p, sz);
m_p += strlen(sz);
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p - puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p - puffer > N - 100) {
ToFile();
}
}
char puffer[N], * m_p;
};
template<int N = 1'000'000>
class CInBuff
{
public:
inline CInBuff() {}
inline CInBuff<N>& operator>>(char& ch) {
FileToBuf();
while (('\r' == *S) || ('\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
ch = *S++;
return *this;
}
inline CInBuff<N>& operator>>(int& val) {
FileToBuf();
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
inline CInBuff& operator>>(long long& val) {
FileToBuf();
long long x(0); int f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
template<class T1, class T2>
inline CInBuff& operator>>(pair<T1, T2>& val) {
*this >> val.first >> val.second;
return *this;
}
template<class T1, class T2, class T3>
inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
return *this;
}
template<class T1, class T2, class T3, class T4>
inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
return *this;
}
template<class T = int>
inline CInBuff& operator>>(vector<T>& val) {
int n;
*this >> n;
val.resize(n);
for (int i = 0; i < n; i++) {
*this >> val[i];
}
return *this;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
*this >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read() {
vector<T> ret;
*this >> ret;
return ret;
}
private:
inline void FileToBuf() {
const int canRead = m_iWritePos - (S - buffer);
if (canRead >= 100) { return; }
if (m_bFinish) { return; }
for (int i = 0; i < canRead; i++)
{
buffer[i] = S[i];//memcpy出错
}
m_iWritePos = canRead;
buffer[m_iWritePos] = 0;
S = buffer;
int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
if (readCnt <= 0) { m_bFinish = true; return; }
m_iWritePos += readCnt;
buffer[m_iWritePos] = 0;
S = buffer;
}
int m_iWritePos = 0; bool m_bFinish = false;
char buffer[N + 10], * S = buffer;
};
class Solution {
public:
vector<int> Ans(vector<vector<int>>& v2) {
const int N = v2.size();
vector<int> a(N);
a[1] = v2[0][1];
vector<int> pmin(N, INT_MAX / 2), pmax(N, INT_MIN / 2);
pmin[0] = 0, pmin[1] = a[1];
pmax[0] = a[1] = pmax[1] = a[1];
for (int i = 2; i < N; i++) {
const int c = v2[i - 1][1];
auto Is = [&](int cur)
{
for (int j = 0; j < i - 1; j++) {
if (v2[j][i - j] != max(pmax[j], cur) - min(pmin[j], cur)) {
return false;
}
}
return true;
};
int cur = 0;
if (Is(a[i - 1] + c)) {
cur = a[i - 1] + c;
}
else {
cur = a[i - 1] - c;
}
a[i] = cur;
for (int j = 0; j <= i; j++) {
pmax[j] = max(pmax[j], cur);
pmin[j] = min(pmin[j], cur);
}
}
return a;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
//ios::sync_with_stdio(0); cin.tie(nullptr);
CInBuff<> in; COutBuff<10'000'000> ob;
int N;
in >> N;
vector<vector<int>> a(N);
for (int i = 0; i < N; i++) {
a[i] = in.Read<int>(N - i);
}
#ifdef _DEBUG
//printf("M=%d,K=%d", M,K);
Out(a, ",a=");
//Out(que, "que=");
//Out(B, "B=");
//Out(que, "que=");
//Out(B, "B=");
#endif // DEBUG
auto res = Solution().Ans(a);
for (const auto& i : res) {
cout << i << " ";
}
return 0;
}
单元测试
cpp
vector<vector<int>> a;
TEST_METHOD(TestMethod1)
{
a = { {0,2,2},{0,1},{0} };
auto res = Solution().Ans(a);
AssertEx({ 0,2,1 }, res);
}
TEST_METHOD(TestMethod2)
{
a = { {0,1,1},{0,0},{0} };
auto res = Solution().Ans(a);
AssertEx({ 0,1,1 }, res);
}
TEST_METHOD(TestMethod3)
{
a = { {0,1,2,2},{0,1,1},{0,1},{0} };
auto res = Solution().Ans(a);
AssertEx({ 0,1,2,1 }, res);
}
TEST_METHOD(TestMethod4)
{
a = { {0,1,1,2},{0,0,2},{0,2},{0} };
auto res = Solution().Ans(a);
AssertEx({ 0,1,1,-1 }, res);
}
扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。