【排序】P9127 [USACO23FEB] Equal Sum Subarrays G|普及+

本文涉及的基础知识点

排序

[USACO23FEB] Equal Sum Subarrays G

题面翻译

题目描述

注意:本题的时间限制为 3 秒,为默认时间的 1.5 倍。

FJ 给了 Bessie 一个长度为 N N N 的数组 a a a( 2 ≤ N ≤ 500 , − 1 0 15 ≤ a i ≤ 1 0 15 2 \leq N \leq 500, -10^{15} \leq a_i \leq 10^{15} 2≤N≤500,−1015≤ai≤1015),其中所有 N ( N + 1 ) 2 \dfrac{N(N+1)}{2} 2N(N+1) 个连续子数组的和都是不同的。对于每个下标 i ∈ [ 1 , N ] i \in [1,N] i∈[1,N],帮助 Bessie 计算最小的改变量,使得数组中存在两个不同的连续子数组的和相等。

输入格式

第一行包含一个整数 N N N,表示数组的长度。

第二行包含 a 1 , ⋯   , a N a_1,\cdots,a_N a1,⋯,aN,即数组 a a a 的元素,按顺序给出。

输出格式

对于每个下标 i ∈ [ 1 , N ] i \in [1,N] i∈[1,N],输出一行一个整数,表示改变 a i a_i ai 的最小改变量。

样例 1 的解释

将 a 1 a_1 a1 减少 2 2 2,可以使得 a 1 + a 2 = a 2 a_1+a_2=a_2 a1+a2=a2。类似地,将 a 2 a_2 a2 增加 3 3 3,可以使得 a 1 + a 2 = a 1 a_1+a_2=a_1 a1+a2=a1。

样例 2 的解释

将 a 1 a_1 a1 增加 1 1 1 或将 a 3 a_3 a3 减少 1 1 1,可以使得 a 1 = a 3 a_1=a_3 a1=a3。将 a 2 a_2 a2 增加 6 6 6,可以使得 a 1 = a 1 + a 2 + a 3 a_1=a_1+a_2+a_3 a1=a1+a2+a3。

评分标准

  • 测试点 3 3 3: N ≤ 40 N \leq 40 N≤40
  • 测试点 4 4 4: N ≤ 80 N \leq 80 N≤80
  • 测试点 5 − 7 5-7 5−7: N ≤ 200 N \leq 200 N≤200
  • 测试点 8 − 16 8-16 8−16:无额外限制。

题目描述

Note: The time limit for this problem is 3s, 1.5x the default.

FJ gave Bessie an array a a a of length N ( 2 ≤ N ≤ 500 , − 1 0 15 ≤ a i ≤ 1 0 15 ) N(2 \le N \le 500,−10^{15} \le a_i \le 10^{15}) N(2≤N≤500,−1015≤ai≤1015) with all N ( N + 1 ) 2 \dfrac{N(N+1)}{2} 2N(N+1) contiguous subarray sums distinct. For each index i ∈ [ 1 , N ] i \in [1,N] i∈[1,N], help Bessie compute the minimum amount it suffices to change ai by so that there are two different contiguous subarrays of a with equal sum.

输入格式

The first line contains N N N.

The next line contains a 1 , ⋯   , a N a_1, \cdots ,a_N a1,⋯,aN

(the elements of a a a, in order).

输出格式

One line for each index i ∈ [ 1 , N ] i \in [1,N] i∈[1,N].

样例 #1

样例输入 #1

复制代码
2
2 -3

样例输出 #1

复制代码
2
3

样例 #2

样例输入 #2

复制代码
3
3 -10 4

样例输出 #2

复制代码
1
6
1

提示

Explanation for Sample 1

Decreasing a 1 a_1 a1 by 2 2 2 would result in a 1 + a 2 = a 2 a_1+a_2=a_2 a1+a2=a2. Similarly, increasing a 2 a_2 a2 by 3 3 3 would result in a 1 + a 2 = a 1 a_1+a_2=a_1 a1+a2=a1.

Explanation for Sample 2

Increasing a1 or decreasing a 3 a_3 a3 by 1 1 1 would result in a 1 = a 3 a_1=a_3 a1=a3. Increasing a 2 a_2 a2 by 6 6 6 would result in a 1 = a 1 + a 2 + a 3 a_1=a_1+a_2+a_3 a1=a1+a2+a3.

SCORING

  • Input 3 3 3: N ≤ 40 N \le 40 N≤40
  • Input 4 4 4: N ≤ 80 N \le 80 N≤80
  • Inputs 5 − 7 5-7 5−7: N ≤ 200 N \le 200 N≤200
  • Inputs 8-16: No additional constraints.

二分查找 前缀和 线段树(思路过于复杂,太难)

任意两个区间[l1,r1]和[l2,r2],l1<=l2,l1<=r1l2<=r2。

情况一:两个区间无交叉。即r1 < l2。    ⟺    \iff ⟺ [l1,r1]之和等于[l2,r2]之和。

情况二:两个区间有包括关系。如:l1 <= l2 <= r2 <=l2,    ⟺    \iff ⟺ a,区间[l1,l2]和[r2,l2]之和为0。b,或一个区间位0,令外一个区间不存在。由于本题所有区间和不存在,故只能是情况a。

情况三:两个区间交叉。    ⟺    \iff ⟺ [l1,l2]之和等于[r1,r2]。

情况三和情况一等效。

情况一和情况三,可以分成a,修改[l1,r1];b,修改[l2,r2]。

,x=区间2的和-区间1的和。将a[l2...r2]任意元素减少x,就相等了。

线段树[i]记录 修改a[i]可以让两个区间相等的最少修改量。

两层循环l2,r2

有序集合s记录所有区间[l1,r1]的前缀和。r1 < l2。

sum = preSum[r2+1]-preSum[l2]。

auto it = s.lower(sum)

x = min(it-sum,sum- (--it))

l2,r2\]的最小值修改为x。 **时间复杂度**:O(nnlog(nn)) ### 关于树状数组 最大值树状数组,我只有单点修改的模板,所以无法使用树状数组。 ### 未完成代码 ```cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; template std::istream& operator >> (std::istream& in, pair& pr) { in >> pr.first >> pr.second; return in; } template std::istream& operator >> (std::istream& in, tuple& t) { in >> get<0>(t) >> get<1>(t) >> get<2>(t) ; return in; } template std::istream& operator >> (std::istream& in, tuple& t) { in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t); return in; } template vector Read() { int n; scanf("%d", &n); vector ret(n); for(int i=0;i < n ;i++) { cin >> ret[i]; } return ret; } template vector Read(int n) { vector ret(n); for (int i = 0; i < n; i++) { cin >> ret[i]; } return ret; } template class CRangUpdateLineTree { protected: virtual void OnQuery(const TSave& save, const int& iSaveLeft, const int& iSaveRight) = 0; virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) = 0; virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) = 0; virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) = 0; }; template class CVectorRangeUpdateLineTree : public CRangUpdateLineTree { public: CVectorRangeUpdateLineTree(int iEleSize, TSave tDefault, TRecord tRecordNull) :m_iEleSize(iEleSize) , m_save(iEleSize * 4, tDefault), m_record(iEleSize * 4, tRecordNull) { m_recordNull = tRecordNull; } void Update(int iLeftIndex, int iRightIndex, TRecord value) { Update(1, 0, m_iEleSize - 1, iLeftIndex, iRightIndex, value); } void Query(int leftIndex, int rightIndex) { Query(1, 0, m_iEleSize - 1, leftIndex, rightIndex); } //void Init() { // Init(1, 0, m_iEleSize - 1); //} TSave QueryAll() { return m_save[1]; } void swap(CVectorRangeUpdateLineTree& other) { m_save.swap(other.m_save); m_record.swap(other.m_record); std::swap(m_recordNull, other.m_recordNull); assert(m_iEleSize == other.m_iEleSize); } protected: //void Init(int iNodeNO, int iSaveLeft, int iSaveRight) //{ // if (iSaveLeft == iSaveRight) { // this->OnInit(m_save[iNodeNO], iSaveLeft); // return; // } // const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; // Init(iNodeNO * 2, iSaveLeft, mid); // Init(iNodeNO * 2 + 1, mid + 1, iSaveRight); // this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO * 2], m_save[iNodeNO * 2 + 1], iSaveLeft, iSaveRight); //} void Query(int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) { if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) { this->OnQuery(m_save[iNodeNO], iSaveLeft, iSaveRight); return; } if (iSaveLeft == iSaveRight) {//没有子节点 return; } Fresh(iNodeNO, iSaveLeft, iSaveRight); const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (mid >= iQueryLeft) { Query(iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight); } if (mid + 1 <= iQueryRight) { Query(iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight); } } void Update(int iNode, int iSaveLeft, int iSaveRight, int iOpeLeft, int iOpeRight, TRecord value) { if ((iOpeLeft <= iSaveLeft) && (iOpeRight >= iSaveRight)) { this->OnUpdate(m_save[iNode], iSaveLeft, iSaveRight, value); this->OnUpdateRecord(m_record[iNode], value); return; } if (iSaveLeft == iSaveRight) { return;//没有子节点 } Fresh(iNode, iSaveLeft, iSaveRight); const int iMid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (iMid >= iOpeLeft) { Update(iNode * 2, iSaveLeft, iMid, iOpeLeft, iOpeRight, value); } if (iMid + 1 <= iOpeRight) { Update(iNode * 2 + 1, iMid + 1, iSaveRight, iOpeLeft, iOpeRight, value); } // 如果有后代,至少两个后代 this->OnUpdateParent(m_save[iNode], m_save[iNode * 2], m_save[iNode * 2 + 1], iSaveLeft, iSaveRight); } void Fresh(int iNode, int iDataLeft, int iDataRight) { if (m_recordNull == m_record[iNode]) { return; } const int iMid = iDataLeft + (iDataRight - iDataLeft) / 2; Update(iNode * 2, iDataLeft, iMid, iDataLeft, iMid, m_record[iNode]); Update(iNode * 2 + 1, iMid + 1, iDataRight, iMid + 1, iDataRight, m_record[iNode]); m_record[iNode] = m_recordNull; } vector m_save; vector m_record; TRecord m_recordNull; const int m_iEleSize; }; template class CMyLineTree : public CVectorRangeUpdateLineTree { public: CMyLineTree(int iSize, TSave iNotMay) :CVectorRangeUpdateLineTree(iSize, iNotMay, iNotMay) { } void Query(int leftIndex, int leftRight) { m_iQuery = CVectorRangeUpdateLineTree::m_recordNull; CVectorRangeUpdateLineTree::Query(leftIndex, leftRight); } TSave m_iQuery; protected: virtual void OnQuery(const TSave& save, const int& iSaveLeft, const int& iSaveRight) { m_iQuery = min(m_iQuery, save); } virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) { save = min(save, update); } virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) { par = min(left, r); } virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) { old = min(newRecord, old); } }; class Solution { public: vector Ans(vector& a) { const int N = a.size(); vector preSum = { 0 }; for (const auto& ii : a) { preSum.emplace_back(ii + preSum.back()); } CMyLineTree lineTree(N, LLONG_MAX / 2); auto DoARang = [&preSum, &lineTree](set& s, int left, int r) { auto cur = LLONG_MAX / 2; const auto curSum = preSum[r + 1] - preSum[left]; auto it = s.upper_bound(curSum); if (s.end() != it) { cur = min(cur, *it - curSum); } if (s.begin() != it) { --it; cur = min(cur, curSum - *it); } lineTree.Update(left, r, cur); }; auto Do1 = [&]() { set s; for (int r = N - 1; r >= 0; r--) {// for (int left = r; left >= 0; left--) { DoARang(s, left, r); } for (int r2 = r; r2 < N; r2++) { s.emplace(preSum[r2 + 1] - preSum[r]); } } }; auto Do2 = [&]() { set s; for (int left = 0; left < N; left++) { for (int r = left; r < N; r++) { DoARang(s, left, r); } for (int left2 = 0; left2 <= left; left2++) { s.emplace(preSum[left + 1] - preSum[left2]); } } }; Do1();//两个区间无交叉,修改第一区间 Do2();//两个区间无交叉,修改第二区间 for (int left = 0; left < N; left++) { for (int r = left; r < N; r++) {//人员长度[1,N-1]的区间改成0,对应一个区间包含令一个区间 if (r - left + 1 == N) { continue; } lineTree.Update(left, r, abs(preSum[r + 1] - preSum[left])); } } vector ans; for (int i = 0; i < N; i++) { lineTree.Query(i, i); ans.emplace_back(lineTree.m_iQuery); } return ans; } }; int main() { #ifdef _DEBUG freopen("a.in", "r", stdin); #endif // DEBUG int n; cin >> n; auto c = Read(n); auto res = Solution().Ans(c); #ifdef _DEBUG //printf("K=%d", K); //Out(b, "b="); //Out(c, "c="); #endif // DEBUG for (const auto& i : res) { printf("%lld\r\n",i); } return 0; } ``` ## 新思路(过于复杂,容易出错) 我们枚举修改a\[i\],则一定存在一个区间包括i,一个区间不包括i。将a\[i\]加或减少区间和的差的绝对值则一定符合题意。 如果选择的两个区间都包括i或都不包括i,则区间和的差不会变。 curs:所有包括a\[i\]的区间和。 mins:所有不包括a\[i\],且右边界\i的期间和。 mins\[i+1\] = mins\[i\]+所有右边界为i。 curs\[i+1\]=curs\[i\]-所有右边界为i+所有左边界为i+1。 maxs\[i+1\]= maxs\[i\]-所有左边界为i+1。 任意区间在mins,curs,maxs中最多增删一次。故时间复杂度:O(nnlognn)。 对于任意i,都比较curs和mins及maxs。 但这样的时间复杂度是O(nnnnnlognn) 解决方法是:只比较变化部分。任意区间顶多变化两次,maxs → \\rightarrow →curs,curs → \\rightarrow →mins。 故时间复杂度:O(nnlognn)。 ## 时间复杂度O(nnn) 向量v各元素记录:区间和,左边界,右边界。 升序排序。 i从0到大枚举修改a\[i

j从0到大枚举v[j]

p1记录上一个包括a[i]的v[j1].区间和,默认值-1e18

p0记录上一个包括不a[i]的v[j1].区间和,默认值-1e18

如果v[j]包括a[i],则ans = min(abs(v[i]区间和-p0)

如果v[j]不包括a[i],则ans = min(abs(v[i]区间和-p1)

代码

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 <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;
	scanf("%d", &n);
	vector<T> ret(n);
	for(int i=0;i < n ;i++) {
		cin >> ret[i];
	}
	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;
}

class Solution {
		public:
			vector<long long> Ans(vector<long long>& a) {
				const int N = a.size();				
				vector<long long> preSum = { 0 };
				for (const auto& ii : a) {
					preSum.emplace_back(ii + preSum.back());
				}
				vector<tuple<long long, int, int>> v;
				for (int left = 0; left < N; left++) {
					for (int r = left; r < N; r++) {
						v.emplace_back(preSum[r + 1] - preSum[left], left, r);
					}
				}
				sort(v.begin(), v.end());				
				vector<long long> ans(N,LLONG_MAX/2);
				for (int i = 0; i < N; i++) {
					long long pre0 = -1e18;
					auto pre1 = pre0;
					for (const auto& [sum, left, r] : v) {
						if ((left <= i) && (i <= r)) {
							ans[i] = min(ans[i], sum - pre0);
							pre1 = sum;
						}
						else {
							ans[i] = min(ans[i], sum - pre1);
							pre0 = sum;
						}
					}
				}
				return ans;
			}
		};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	int n;
	cin >> n;
	auto c = Read<long long>(n);
	auto res = Solution().Ans(c);
#ifdef _DEBUG		
	//printf("K=%d", K);
	//Out(b, "b=");
	//Out(c, "c=");
#endif // DEBUG	
	for (const auto& i : res) {
		printf("%lld\r\n",i);
	}
	return 0;
}

单元测试

cpp 复制代码
vector<long long> a;
		TEST_METHOD(TestMethod11)
		{
			a = { 2,-3};
			auto res = Solution().Ans(a);
			AssertEx({ 2,3 }, res);
		}
		TEST_METHOD(TestMethod12)
		{
			a = { 3,-10,4 };
			auto res = Solution().Ans(a);
			AssertEx({ 1,6,1 }, res);
		}
		TEST_METHOD(TestMethod13)
		{
			a = { (long long)1e15,(long long)1e15 };
			auto res = Solution().Ans(a);
			AssertEx({ 0,0 }, res);
		}
		TEST_METHOD(TestMethod14)
		{
			a = { (long long)-1e15,(long long)1e15-1 };
			auto res = Solution().Ans(a);
			AssertEx({ (long long)1e15,(long long)1e15-1 }, res);
		}
		TEST_METHOD(TestMethod15)
		{
			a = { 1001,2000 };
			auto res = Solution().Ans(a);
			AssertEx({ 999,999 }, res);
		}
		TEST_METHOD(TestMethod16)
		{
			a = { 1001,2000,4000 };
			auto res = Solution().Ans(a);
			AssertEx({ 999,999,999 }, 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++**实现。

相关推荐
moringlightyn3 小时前
c++ 智能指针
开发语言·c++·笔记·c++11·指针·智能指针
Code_Shark3 小时前
AtCoder Beginner Contest 424 题解
数据结构·c++·算法·数学建模·青少年编程
CS创新实验室4 小时前
深入解析快速排序(Quicksort):从原理到实践
数据结构·算法·排序算法·快速排序
今天又在学代码写BUG口牙4 小时前
MFC应用程序,工作线程学习记录
c++·mfc·1024程序员节
j_xxx404_4 小时前
C++ STL简介:从原理到入门使用指南
开发语言·c++
15Moonlight4 小时前
06-MySQL基础查询
数据库·c++·mysql·1024程序员节
Dream it possible!4 小时前
LeetCode 面试经典 150_链表_反转链表 II(60_92_C++_中等)(头插法)
c++·leetcode·链表·面试
十五年专注C++开发5 小时前
Drogon: 一个开源的C++高性能Web框架
linux·c++·windows·后端开发·服务器开发
Theodore_10225 小时前
深度学习(3)神经网络
人工智能·深度学习·神经网络·算法·机器学习·计算机视觉