本文涉及的基础知识点
排序
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的和。将al2...r2任意元素减少x,就相等了。
线段树i记录 修改ai可以让两个区间相等的最少修改量。
两层循环l2,r2
有序集合s记录所有区间l1,r1的前缀和。r1 < l2。
sum = preSumr2+1-preSuml2。
auto it = s.lower(sum)
x = min(it-sum,sum- (--it))
l2,r2的最小值修改为x。
时间复杂度:O(nnlog(nn))
关于树状数组
最大值树状数组,我只有单点修改的模板,所以无法使用树状数组。
未完成代码
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;
}
template<class TSave, class TRecord >
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 TSave, class TRecord >
class CVectorRangeUpdateLineTree : public CRangUpdateLineTree<TSave, TRecord>
{
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<TSave, TRecord>& 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<TSave> m_save;
vector<TRecord> m_record;
TRecord m_recordNull;
const int m_iEleSize;
};
template<class TSave = int, class TRecord = int >
class CMyLineTree : public CVectorRangeUpdateLineTree<TSave, TRecord>
{
public:
CMyLineTree(int iSize, TSave iNotMay) :CVectorRangeUpdateLineTree<TSave, TRecord>(iSize, iNotMay, iNotMay) {
}
void Query(int leftIndex, int leftRight) {
m_iQuery = CVectorRangeUpdateLineTree<TSave, TRecord>::m_recordNull;
CVectorRangeUpdateLineTree<TSave, TRecord>::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<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());
}
CMyLineTree<long long, long long> lineTree(N, LLONG_MAX / 2);
auto DoARang = [&preSum, &lineTree](set<long long>& 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<long long> 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<long long> 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<long long> 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<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;
}
新思路(过于复杂,容易出错)
我们枚举修改ai,则一定存在一个区间包括i,一个区间不包括i。将ai加或减少区间和的差的绝对值则一定符合题意。
如果选择的两个区间都包括i或都不包括i,则区间和的差不会变。
curs:所有包括ai的区间和。
mins:所有不包括ai,且右边界<i的区间和。
max:所有不包括ai,且左边界>i的期间和。
minsi+1 = minsi+所有右边界为i。
cursi+1=cursi-所有右边界为i+所有左边界为i+1。
maxsi+1= maxsi-所有左边界为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到大枚举修改ai
j从0到大枚举vj
p1记录上一个包括ai的vj1.区间和,默认值-1e18
p0记录上一个包括不ai的vj1.区间和,默认值-1e18
如果vj包括ai,则ans = min(abs(vi区间和-p0)
如果vj不包括ai,则ans = min(abs(vi区间和-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++**实现。