POJ-2796 & 2019南昌邀请赛网络赛 I. 区间最大min*sum
http://poj.org/problem?id=2796
https://nanti.jisuanke.com/t/38228
背景
给定一个序列,对于任意区间,min表示区间中最小的数,sum表示区间和,求使得min*sum最大的区间或区间值。
POJ-2796中,序列的值非负,而在网络赛I题中,序列存在负值。
解法分析
直觉上,后者是前者的拓展,我们先考虑序列非负的情况。
非负情况
设序列存储在数组a中。当我们考虑数值a[i]作为区间最小值时,显然我们应该向i的左右两侧扩展并终止于遇到更小的值或者数组越界之前,这样得到的区间可以保证a[i]为最小值且区间和最大。
但是每次都如此求解,复杂度为$O\left ( n^2 \right )$。因此我们尝试用单调栈将其降低到$O\left ( n \right )$。
从我们刚刚的分析中可以看出,最小值是求解区间的关键,同时由于我们是顺序对数组进行处理(以从左往右为例),我们可以利用单调栈,在当遇到a[i]时,很快地找到左侧最近的比它更小的数值(具体而言,构建一个约束为单调递增的栈,当遇到a[i]时,逐个pop掉比它大的数),那么我们可以进一步拓展,借助单调栈维护以a[i]为右端点且为最小值的区间和,我们将栈中节点表示为Node{min,sum}(原单调栈中只存储数值,现在我们要额外存储这个区间的区间和),也就是上文中i向左侧扩展的区间情况,我们将被pop的node.sum求和再加上a[i]就得到了node[i]的左侧区间和了。但是还有i右侧的情况呢,我们先把node[i]压入栈继续处理之后的数字,注意到在随后的处理中当且仅当遇到了第一个比a[i]要小的数字时,node[i]会被pop出来,那么此轮pop中比node[i]先pop出来的若干Node合并在一起显然就是i右侧的区间了,两者进一步合并就得到了以a[i]为最小值,min*sum最大的区间了。需要额外注意的是,一个Node可能右侧没有比它更小的数值,那么在算法最后,需要将栈逐个pop出来做如上操作。
此外POJ-2796还需要计算区间端点位置(如有多个可选区间,任意输出),那么在Node中额外记录一下即可,不做过多说明。
由于每一个元素最多入栈一次,一旦被pop就不会再被查询到,显然复杂度为$O\left ( n \right )$。
代码的main函数,为了迁移到下文的题目中,做了额外修改,本节可以忽略。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<queue>
#include<map>
#include<cmath>
#include<set>
#include<stack>
#define LL long long
using namespace std;
const int N = ;
LL n, m;
LL a[N];
struct node
{
LL minv;
LL sum;
LL lef;
node(LL mv,LL s,LL l)
{
minv=mv,sum=s,lef=l;
}
};
//10 4 8 3 2 6 8 4 9 3 7
int al,ar;
LL glo_ans;
void cal(int l,int r)
{
stack<node> s;
LL pop_sum,pop_lef;
for(int i=l; i<=r; i++)
{
pop_sum=;
pop_lef=i;
while(!s.empty()&&a[i]<=s.top().minv)
{
node p=s.top();
s.pop();
pop_lef=p.lef;
pop_sum+=p.sum;
if(pop_sum*p.minv>glo_ans)
glo_ans=pop_sum*p.minv,al=p.lef,ar=i-;
//ans=max(ans,pop_sum*p.minv);
}
s.push(node(a[i],a[i]+pop_sum,pop_lef));
}
pop_sum=;
while(!s.empty())
{
node p=s.top();
s.pop();
pop_sum+=p.sum;
if(pop_sum*p.minv>glo_ans)
glo_ans=pop_sum*p.minv,al=p.lef,ar=r;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//cin.sync_with_stdio(false);
int n;
while(scanf("%d",&n)!=EOF)
{
glo_ans=,al=ar=;
for(int i=; i<=n; i++)
scanf("%lld",&a[i]); int pl=-,pr=;
for(int i=; i<=n; i++)
{
if(a[i]>)
{
if(pl==-)
pl=i;
pr=i;
if(i==n||a[i+]<=)
{
cal(pl,pr);
}
}
else
pl=-;
} //cal(1,n);
LL tx=,mx=a[al];
for(int i=al;i<=ar;i++)
tx+=a[i],mx=min(mx,a[i]);
printf("%lld\n",glo_ans);
//cout<<glo_ans<<endl;
printf("%d %d\n",al,ar);
//cout<<al<<' '<<ar<<endl;
}
return ;
}
存在负数的情况
存在负数的序列则不能简单地认为可以向左右随意扩张,因为sum不一定随着区间扩张而增长。但经过分析,我们可以发现,无论序列中的数值是什么,最大的min*sum一定是一个非负数,那么只可能有正数乘以正数或负数乘以负数的情况(答案为零的情况无需额外求解,初始值设置为0即可)。
前者,我们可以将序列分成若干非负子区间,用上文的方法求解。
后者,对于任意负数,随着区间的扩张,最小值只会减小不会增大,这样我们只要枚举每一个负数并假设a[i]为最小值,找到区间和最小的区间,求出min*sum,在枚举中保留最大答案即可(与正数的情况不同,我们扩张数组不必担心会使最小值变大,只用考虑区间和的问题),不必担心找到的区间具有更小的最小值,因为我们枚举了每一个负数,那么更小的最小值必然也会被枚举,不会漏掉答案。那么如何找到区间和最小的区间呢?我们可以计算数组的前缀和以及后缀和,i左侧后缀和最小以及i右侧前缀和最小的位置构成的区间既是区间和最小区间,为了快速索引,可以再引入两个数组用于记录i左侧后缀/右侧前缀和最小的位置。
#include <iostream>
#include <vector>
#include <stack>
#define LL long long
using namespace std;
const int N=;
LL inf=;
LL pre[N],las[N];
LL idp[N],idl[N];
LL a[N];
int n;
struct node
{
LL minv;
LL sum;
node(LL mv,LL s)
{
minv=mv,sum=s;
}
}; LL glo_ans;
void cal(int l,int r)
{
//cout<<l<<' '<<r<<endl;
stack<node> s;
LL pop_sum;
for(int i=l; i<=r; i++)
{
pop_sum=;
while(!s.empty()&&a[i]<=s.top().minv)
{
node p=s.top();
s.pop();
pop_sum+=p.sum;
if(pop_sum*p.minv>glo_ans)
glo_ans=pop_sum*p.minv;
//ans=max(ans,pop_sum*p.minv);
}
s.push(node(a[i],a[i]+pop_sum));
}
pop_sum=;
while(!s.empty())
{
node p=s.top();
s.pop();
pop_sum+=p.sum;
if(pop_sum*p.minv>glo_ans)
glo_ans=pop_sum*p.minv;
}
} int main()
{
while(scanf("%d",&n)!=EOF)
{
LL ans = ;
glo_ans=-inf;
for(int i=; i<=n; i++)
{
scanf("%lld",&a[i]);
idp[i]=idl[i]=i;
glo_ans=max(glo_ans,a[i]);
}
int pl=-,pr=;
for(int i=; i<=n; i++)
{
if(a[i]>)
{
if(pl==-)
pl=i;
pr=i;
if(i==n||a[i+]<=)
{
cal(pl,pr);
}
}
else
pl=-;
}
fill(las,las+n+,);
fill(pre,pre+n+,);
for(int i=; i<=n; i++)
pre[i]=pre[i-]+a[i];
for(int i=n; i>=; i--)
las[i]=las[i+]+a[i];
for(int i=; i<=n; i++)
if(las[i]>las[idl[i-]])
idl[i]=idl[i-];
for(int i=n-; i>=; i--)
if(pre[i]>pre[idp[i+]])
idp[i]=idp[i+];
for(int i=; i<=n; i++)
{
if(a[i]<)
{
LL lef=las[idl[i]]-las[i+];
LL rig=pre[idp[i]]-pre[i-]; LL sm=lef+rig-a[i]; ans=max(ans,sm*a[i]);
}
}
printf("%lld\n",max(ans,glo_ans));
}
return ;
}
POJ-2796 & 2019南昌邀请赛网络赛 I. 区间最大min*sum的更多相关文章
- 2019南昌邀请赛网络赛:J distance on the tree
1000ms 262144K DSM(Data Structure Master) once learned about tree when he was preparing for NOIP(N ...
- [2019南昌邀请赛网络赛D][dp]
https://nanti.jisuanke.com/t/38223 Xiao Ming recently indulges in match stick game and he thinks he ...
- 2019南昌邀请赛网络预选赛 M. Subsequence
传送门 题意: 给出一个只包含小写字母的串 s 和n 个串t,判断t[i]是否为串 s 的子序列: 如果是,输出"YES",反之,输出"NO": 坑点: 二分一 ...
- 南昌邀请赛网络赛 D.Match Stick Game(dp)
南昌邀请赛网络赛 D.Match Stick Game 题目传送门 题目就会给你一个长度为n的字符串,其中\(1<n<100\).这个字符串是一个表达式,只有加减运算符,然后输入的每一个字 ...
- 2019 ICPC南昌邀请赛网络赛比赛过程及题解
解题过程 中午吃饭比较晚,到机房lfw开始发各队的账号密码,byf开始读D题,shl电脑卡的要死,启动中...然后听到谁说A题过了好多,然后shl让blf读A题,A题blf一下就A了.然后lfw读完M ...
- 计蒜客 2019南昌邀请网络赛J Distance on the tree(主席树)题解
题意:给出一棵树,给出每条边的权值,现在给出m个询问,要你每次输出u~v的最短路径中,边权 <= k 的边有几条 思路:当时网络赛的时候没学过主席树,现在补上.先树上建主席树,然后把边权交给子节 ...
- 2019 ICPC南昌邀请赛 网络赛 K. MORE XOR
说明 \(\oplus x\)为累异或 $ x^{\oplus(a)}$为异或幂 题意&解法 题库链接 $ f(l,r)=\oplus_{i=l}^{r} a[i]$ $ g(l,r)=\ ...
- icpc 南昌邀请赛网络赛 Max answer
就是求区间和与区间最小值的积的最大值 但是a[i]可能是负的 这就很坑 赛后看了好多dalao的博客 终于a了 这个问题我感觉可以分为两个步骤 第一步是对于每个元素 以它为最小值的最大区间是什么 第二 ...
- icpc 南昌邀请赛网络赛 Subsequence
题目链接:https://nanti.jisuanke.com/t/38232 就是判断输入是不是子序列 没想到贡献了将近十几次罚时..........可以说是菜的真实了 用cin cout超时了 改 ...
随机推荐
- dubbo搭建
1.安装java : yum install java 2.下载Tomcat: wget http://mirrors.shu.edu.cn/apache/tomcat/tomcat-9/v9.0.1 ...
- js常用的400个特效
JavaScript实现可以完全自由拖拽的效果,带三个范例 http://www.sharejs.com/showdetails-501.aspx javascript实现可以自由拖动的树形列 ...
- C# Json处理相关
最近工作中遇到的Json问题确实很头大,主要是各种转义符的处理,想了一种通用的方式,来处理任意转移方式的Json字符串: /// <summary> /// 去除返回值中的转义符,返回js ...
- pycharm的断点调试与TODO标记
断点调试的方法: 断点调试在程序比较大的时候调试运用的比较多 点击Pycharm软件右上角绿色三角形右边的小甲鱼图标,点击之后会弹出断点调试的界面 Debug是用来调试bug的 terminal 是终 ...
- win7插着网线开机卡死,拔下网线开机正常
公司的部分win7电脑插着网线开机,进到桌面后网络图标转圈圈卡住.控制面板,启动项,任务管理器等都打不开.把网线拔下后再开机,电脑正常进入系统,后再插上网线就能正常上网了.被这个问题困扰了很久,百度也 ...
- WebDriver实现网页自动化测试(以python为例说明,ruby用法类似)
什么是Webdriver? Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是 Selenium 的 ...
- PIL模块
处理图片的模块 打开图片 im=Image.open("1.png") 创建字体对象 先要字体文件 font = ImageFont.truetype('C:\\WINDOWS\\ ...
- [游戏开发日志]Windows下Cocos2d-x 3.14环境搭建
总介绍 我们小组使用的是cocos2d-x的游戏开发引擎,因此在所有开发工作之前,我们需要对这个引擎进行环境的搭建. 搭建过程 VS2013的下载和安装 VS只是作为一个开发环境而已,简单来说就是敲代 ...
- GO : 斐波纳契数列
package main import "fmt" // fibonacci is a function that returns // a function that retur ...
- drf 单表
^_^ # [{title,price},{}] 构造的数据结构 简单的FBV/CBV def showbooks(request): # FBV if request.method =='GET': ...