【构造 前缀和】P8902 [USACO22DEC] Range Reconstruction S|普及+

本文涉及知识点

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++**实现。

相关推荐
摸鱼仙人~1 小时前
动态规划求解 20 个通用模板
算法·动态规划
记忆多2 小时前
c++内联函数
算法
仟濹2 小时前
【算法打卡day20(2026-03-12 周四)算法/技巧:哈希表,双指针,字符串交换处理】5个题
数据结构·算法·散列表
老四啊laosi2 小时前
[C++进阶] 16. 继承
c++·继承
陌夏2 小时前
双指针与滑动窗口
算法
MicroTech20252 小时前
MLGO微算法科技,推出革命性量子算法ANQITE,推动量子计算新时代
科技·算法·量子计算
实心儿儿2 小时前
C++ —— 继承
开发语言·c++
AMoon丶2 小时前
C++基础-类、对象
java·linux·服务器·c语言·开发语言·jvm·c++
17(无规则自律)2 小时前
Leetcode第二题:用 C++ 解决字母异位词分组
c++·leetcode·哈希算法