USACO23FEB-Gold/洛谷P9127 Equal Sum Subarrays
涉及知识点:贪心,递推(?
写在前面
-
这题虽然是一道黄题(其实应该评绿的),但这是对于 的做法而言;这篇博客的做法是 ,个人觉得大概算蓝。
-
这个做法细节很多,我七七八八算下来交了十几发才过,思路和另一个大佬有些像,但是相对于他的博客进行了一些优化,并且更详细地解释了这种 的做法。
题意
给你一个数组 ,初始时数组的所有区间的区间和互不相同,对于每个 ,将 改为 使得数组会有两个区间的区间和相等,求 的最小值。
性质
-
我们很容易想到一个性质:称我们修改的数叫 ,最后区间和相等的两个区间一定是一个区间包含 ,一个不包含。原因很简单,因为对 修改过后包含 的所有区间的区间和都会变化 ,假设两个区间都包含,那么他们这两个区间的相对大小和修改前一样,而修改前题目保证了没有任何区间和相等,因此同时包含 的两个区间修改后不可能区间和相等。
-
由于题目只让我们输出修改的最小差值,而不用输出具体方案,我们找到一个性质:两个区间有交的情况一定能归纳为无交的情况。
(1). 考虑两个区间交叉的情况
如图,由于我们修改的 由于上述性质不能同时被两个区间包含,那么对于修改后区间和相等的两个红色区间,我们同时减去蓝色虚线括起来的中间部分,就能归纳为两个无交的橙色区间修改后的区间和相等。
(2). 考虑一个区间包含另一个区间的情况
如图,对于这两个红色区间,可以归纳为两个橙色区间修改后的区间和互为相反数。
-
两个区间的区间和互为相反数的注意事项 (注意这里的“区间”概念同样也指一个数,即区间 ):如果两个区间,修改后区间和相加为 ,它们要对答案产生贡献仅当这两个区间中间还有其他数,或者这两个区间旁边还有其他数。
很显然,如果这两个区间中间有其他数则可归纳为性质二的区间包含的情况;而如果这两个区间是挨在一起的(如下图,红色和绿色代表这两个区间,粉色代表其他数),那么这两个区间可以合并为一个区间和为 的区间,加上旁边的数(粉色)就等于这个旁边的数,符合题目要求;而如果没有旁边的数,换句话说就是这两个区间已经占完了整个序列,这时候这两个区间的区间和等于 就没用了。
概括一下就是:要使得这两个区间的并集的补集非空。
做法
掌握了以上几个性质,我们总结一下最终我们需要干些什么:
找出两个不相交的区间,一个包含 ,一个不含。要么使得 修改后两个区间的区间和相等,要么使得修改后两个区间的区间和互为相反数并且满足性质 3
我们枚举 作为分界点,将 之前(不包括 )的所有区间都放入一个 set
里面,再枚举 ,相当于枚举了所有以 开头的区间。此时有两种区间,一种是 左边的区间,一种是右边的区间 。我们用前缀和 算出右边区间的区间和,再在 set
中 二分找出与它区间和最接近的区间,求出它们区间和的差值,这个差值意味着:“ 的区间和要等于另一个区间的区间和的最小差值”,注意这个“差值”可以应用于 当中的任意一个数,因为 中的任意一个数加上这个差值都可以使得 的区间和等于另一个区间的区间和,所以所有 都要被这个差值更新一次。
但我们没有必要因此写数据结构维护 的最小值,容易发现当 固定的时候, 会被 更新一遍,所以我们只需要倒序枚举 即可。
举例:
只会被 更新
会被 更新
会被 更新
我们只需要倒序枚举 ,维护一个后缀 即可
同理,前面我们找了区间和差值最小的两个区间,我们将 的区间和取相反数,即可找区间和加起来等于 的最小修改代价。具体实现代码与上文相同,只是确定了两个区间后碍于性质 3 我们需要判断他们的并集是否覆盖了全部区间。
最后,需要注意一点,最开始的时候 需要设为 ,表示它把自己变为 的代价(题目似乎保证了 ,不用与担心性质 3 冲突)。
代码
最后放上代码,小提醒:由于涉及大量区间操作,for
的时候注意区间的范围、区间的开闭。
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1; ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x){
if(x<0){x=-x;putchar('-');}
if(x>9) wt(x/10);
putchar(x%10+'0');
}
typedef long long LL;
const LL LLINF=numeric_limits<LL>::max()-1;
const int MAXN=505;
int n;
struct Node{
LL val;
int l,r;
bool operator < (const Node &y) const{
return val<y.val;
}
};
LL a[MAXN],pre[MAXN],ans[MAXN];
set<Node>s;
inline bool is_range_merge_cover_all(int l1,int r1,int l2,int r2){
if(l1>l2) swap(l1,l2),swap(r1,r2);
// cout<<l1<<' '<<r1<<' '<<l2<<' '<<r2<<endl;
if(r1+1<l2) return false;
if(l1<=1 && max(r1,r2)>=n) return true;
return false;
}
int main(){
rd(n);
if(n==2){//特判n==2
rd(a[1]);rd(a[2]);
cout<<min(abs(a[1]),abs(a[2]-a[1]))<<endl<<min(abs(a[2]),abs(a[2]-a[1]));
return 0;
}
pre[0]=0;
for(int i=1;i<=n;i++){
rd(a[i]);
ans[i]=abs(a[i]);//将自己变为0的代价
pre[i]=pre[i-1]+a[i];
}
LL tmp,minx;
set<Node>::iterator res;
s.insert({a[1],1,1});
for(int l=2;l<=n;l++){
minx=LLINF;
for(int r=n;r>=l;r--){
tmp=pre[r]-pre[l-1];
minx=min(minx,abs(tmp));//这一步的原因是将这个数变为0过后任意相邻区间加它都等于自己
res=s.lower_bound({tmp,-1,-1});//找后继
if(res!=s.end()) minx=min(minx,abs(res->val-tmp));
if(res!=s.begin()){
res--;//找前驱
minx=min(minx,abs(res->val-tmp));
}
ans[r]=min(ans[r],minx);
tmp=-tmp;//找与这个区间和的相反数最接近的区间和,使他们抵消成为0的代价最小
res=s.lower_bound({tmp,-1,-1});//找后继
if(res!=s.end())
if(!is_range_merge_cover_all(l,r,res->l,res->r))
minx=min(minx,abs(res->val-tmp));//如果这两个区间覆盖了所有数则无法满足题目条件
if(res!=s.begin()){
res--;//找前驱
if(!is_range_merge_cover_all(l,r,res->l,res->r))
minx=min(minx,abs(res->val-tmp));
}
ans[r]=min(ans[r],minx);
}
for(int i=1;i<=l;i++)
s.insert({pre[l]-pre[i-1],i,l});
}
s.clear();
s.insert({a[n],n,n});
for(int r=n-1;r>=1;r--){
minx=LLINF;
for(int l=1;l<=r;l++){
tmp=pre[r]-pre[l-1];
minx=min(minx,abs(tmp));
res=s.lower_bound({tmp,-1,-1});//找后继
if(res!=s.end()) minx=min(minx,abs(res->val-tmp));
if(res!=s.begin()){
res--;//找前驱
minx=min(minx,abs(res->val-tmp));
}
ans[l]=min(ans[l],minx);
tmp=-tmp;
res=s.lower_bound({tmp,-1,-1});//找后继
if(is_range_merge_cover_all(l,r,res->l,res->r)) continue;
if(res!=s.end())
if(!is_range_merge_cover_all(l,r,res->l,res->r))
minx=min(minx,abs(res->val-tmp));
if(res!=s.begin()){
res--;//找前驱
if(!is_range_merge_cover_all(l,r,res->l,res->r))
minx=min(minx,abs(res->val-tmp));
}
ans[l]=min(ans[l],minx);
}
for(int i=n;i>=r;i--)
s.insert({pre[i]-pre[r-1],r,i});
}
for(int i=1;i<=n;i++) wt(ans[i]),putchar('\n');
return 0;
}
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/17388499.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步