题目大意:

给出N个正整数a[1..N],再给出一个正整数k,现在可以进行如下操作:每次选择一个大于k的正整数a[i],将a[i]减去1,选择a[i-1]或a[i+1]中的一个加上1。经过一定次数的操作后,问最大能够选出多长的一个连续子序列,使得这个子序列的每个数都不小于k。M组询问,每组给出一个k

n<=1000000,m<=50,xi,k<=1e8

分析:

乍一看,有一些选择性,还有序列,像是dp,但是对于动规,因为可以经过无数次操作,而且数的范围也不小,完全没有想法。

所以这个时候,我们要再分析一下题意。

发现,无论怎么加减,这个序列的总和是不变的。而且,我们可以随意地进行无数次加减,可以将所有的高出k的数都可以填到低于k的数。

又发现,最终的合法子序列,就是这个序列区间内,每个数都要大于等于k,也就是说,这个区间的平均值必须大于等于k。

所以,我们用前缀和sum[i],表示原数列中前i个元素的和。

这样,我们就相当于枚举点对i,j,j<=i,使得在sum[i]-sum[j]>=k*(i-j)的情况下,i-j最大。(这是等价的,因为无论如何,我们会有一个子区间内的总和是不变的。所以可以采用原始的序列进行操作)

又,为了避免麻烦,我们可以将所有的数都减去k,这样就转化成了,找到最大的i-j,满足sum[i]-sum[j]>=0;

思路一:

枚举i,j点对。复杂度O(m*n^2) 直接慢的飞起。

思路二:

考虑优化,线性的东西,往往要考虑单调栈,或者是单调队列。

发现,对于一个枚举的右端点i,我们要枚举左端点1~i,之后还要重复枚举,只为了取max。是否有单调性?

发现,我们这里要考虑两个方面,一个是sum[i]-sum[j]>=0 一个是:i-j最大。所以从左端点的位置,和它的sum值关系入手。

发现,当k<j,且sum[k]<sum[j]时,k作为左端点,一定比j作为左端点更优,这样的j就可以扔掉。

于是,我们可以维护一个单调栈,正序加入,它从栈低到栈顶记录可能成为答案的左端点编号,从下往上,编号大小递增,sum值递减。

这样对于一个新枚举的i,我们要找到sum[i]-sum[j]>=0且最小的j,栈内二分即可。

复杂度O(m*n*logn) 可惜还是差一点。

思路三:

继续优化思路二。m、n看来是不太好优化了,logn的二分是否可以去掉呢?

发现,即使思路二用了单调栈,但是我们有的时候,还是会用一些“其实一眼就可以看出来的”不能可能成为答案的右端点来进行二分。

什么意思呢?

我们必须考虑右端点枚举的方式,是否也有同样的单调性??

发现,如果,j<i,并且sum[j]<sum[i],那么,j肯定也不会参与答案。

嗯,,,两个单调性,,,

我们可以开两个栈!!

除了上一个栈,我们还可以开一个储存可能成为右端点的栈。

其中,这个栈的更新,正序循环预处理。和上一个栈一样,从下到上,编号单增,sum单减。

我们同理,正序循环预处理第一个左端点栈。

然后进行答案选择,取出右端点的栈顶,不断pop左栈,直到sum[sta1[top1]]>sum[sta2[top2]],刚才最后pop的元素就可能成为答案。

至于为什么能pop,因为后面的右栈中的sum越来越大,而要使左端点越小越好,所以它们一定能过sum小的那些关,就直接跳过了。

思路四:

其实不用那么麻烦开两个栈。

我们初始化左端点栈之后,倒序循环右端点,同上不断pop,最后pop掉的左端点就用来尝试更新答案。

每次当sum[右端点]是历史最大值的时候才操作。

为什么是对的?

因为,右端点即将变小,小了之后,必须sum更大才有可能继续突破更大的sum[左端点],,否则,i小了,sum不增大,答案必然不优。

这样就避免了两个栈的麻烦。

注意:

开long long

代码:(代码好抄,思维难移)

#include<bits/stdc++.h>
using namespace std;
const int N=+;
const int M=;
typedef long long ll;
int a[N];
ll sum[N];
int top,sta[N];
int n,m;
ll aver;
void work(int k)
{
top=;
for(int i=;i<=n;i++)
{
sum[i]=sum[i-]+a[i]-k;
if(!top||sum[sta[top]]>sum[i]){
sta[++top]=i;
}
}//预处理左端点
ll ans=,mx=-2e18-;
for(int i=n;i>=;i--){
if(sum[i]>=){
ans=max(ans,(ll)i);
}
if(sum[i]>mx){
for(;top&&sum[i]-sum[sta[top]]>=;top--){
ans=max(ans,(ll)i-sta[top]);
}
mx=sum[i];
}
}//倒序枚举右端点,尝试产生答案
printf("%lld ",ans);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++) {
scanf("%d",&a[i]),aver+=a[i];
}
aver/=n;
int k;
for(int i=;i<=m;i++){
scanf("%d",&k);
if(k<=aver){
printf("%d ",n);continue;
}//这里是个优化,如果k<=全集区间平均值,那么答案必然是n
work(k);
}
}

总结:

1.对于一个题目,我们应该去先想这是什么类型的题,但是更关键的是,挖掘题目中的隐含信息,比如这个题的平均值。可谓最厉害的算法,就是对症下药。

2.对于线性问题的优化,可以想到的有:单调队列,单调栈,斜率优化,分块,莫队,决策单调性(不会),四边形不等式(??),可行性dp转化为最优性dp,前缀和也算。

3.如果一步优化不完,一般不要轻易放弃这个思路,毕竟好题不是那么直白。考虑针对复杂度的哪个局限部位进行优化。继续挖掘信息。

4.尽量深刻理解算法本质,比如这个题,就很好的用到了两次单调栈的特点。

[POI2010]KLO-Blocks——一道值得思考的题的更多相关文章

  1. 一道值得思考的fork()面试题

    程序如下,判断输出多少个'_' ./a.out int main(){ ; i < ; ++i){ fork(); printf("_"); } } 熟悉fork的话,这里很 ...

  2. 洛谷P2918 [USACO08NOV]买干草(一道完全背包模板题)

    题目链接 很明显的一道完全背包板子题,做法也很简单,就是要注意 这里你可以买比所需多的干草,只要达到数量就行了 状态转移方程:dp[j]=min(dp[j],dp[j-m[i]]+c[i]) 代码如下 ...

  3. 又一道区间DP的题 -- P3146 [USACO16OPEN]248

    https://www.luogu.org/problemnew/show/P3146 一道区间dp的题,以区间长度为阶段; 但由于要处理相邻的问题,就变得有点麻烦; 最开始想了一个我知道有漏洞的方程 ...

  4. [真题] 一道 vsftp 运维题

    一道 vsftp 运维题 一.前言 在 V 站上凑巧看到了好友发的求助帖,五天时间一个理他的都没有.哈哈哈~ 废话不多说,我们来试试. 二.题目 这里我们假设存在这样的场景: 网络内有普通用户 ade ...

  5. QDUOJ 一道简单的数据结构题 栈的使用(括号配对)

    一道简单的数据结构题 发布时间: 2017年6月3日 18:46   最后更新: 2017年6月3日 18:51   时间限制: 1000ms   内存限制: 128M 描述 如果插入“+”和“1”到 ...

  6. 值得一做》关于一道DP+SPFA的题 BZOJ1003 (BZOJ第一页计划) (normal-)

    这是一道数据范围和评测时间水的可怕的题,只是思路有点难想,BUT假如你的思路清晰,完全了解怎么该做,那就算你写一个反LLL和反SLE都能A,如此水的一道题,你不心动吗? 下面贴出题目 Descript ...

  7. 一些值得思考的"小题"一

    如下是我们查找数组中某个元素的一种通常做法 const int *Find(const int *array, int length, int x) { const int *p = array; ; ...

  8. hihocoder第220周-一道拧巴的题

    一.220周 题目链接 问题描述 键盘上有N个数字按键,每个按键只能按一次,每次可以按下多个键,请输出所有可能的按键情况. 输入一个整数N(N在1~8之间),输出全部的按键可能.例如:输入3,输出为 ...

  9. Convex 一道阿姆斯特朗回旋好题

    2001年5月8日,阿姆斯特朗(Armstrong, 1929-2013) 教授发明了一种名为“阿姆斯特朗回旋加速喷气式阿姆斯特朗加密”的加密算法,算法从未公开,直至2013阿姆斯特朗教授逝世后,其生 ...

随机推荐

  1. 【JVM.3】虚拟机性能监控与故障处理工具

    一.概述 经过前面两章对于虚拟机内存分配与回收技术各方面的介绍,相信读者已经建立了一套比较完整的理论基础.理论总是作为指导实践的工具,能把这些执行应用到实际工作中才是我们的最终目的.接下来我们会从实践 ...

  2. 并行管理工具——pdsh

    1. pdsh安装2. pdsh常规使用2.1 pdsh2.2 pdcp 并行管理的方式有很多种: 命令行 一般是for循环 脚本 一般是expect+ssh等自编辑脚本 工具 pssh,pdsh,m ...

  3. C#编写WINNT服务,随便解决安卓开发遇到的5037被众多程序无节操占用的问题

    需求分析: 最近重新开始学习安卓开发,好久不用的ADT集成开发环境频繁遇到不能在仿真机和真机上调试的问题,也就是本人另一篇博文描述的ADB(Android Debug Bridge)监控的5037被金 ...

  4. centos7下安装php+memcached简单记录

    1)centos7下安装php 需要再添加一个yum源来安装php-fpm,可以使用webtatic(这个yum源对国内网络来说恐怕有些慢,当然你也可以选择其它的yum源) [root@nextclo ...

  5. IDEA启动服务阻塞,断点过程十分慢的问题

    使用debug无法启动项目但是使用run就可以启动程序,而且启动比以前的debug模式快的多 原因: 启动不了的原因是在项目中的方法上打了断点,导致项目无法继续编译 取消方法断点就可以了 在idea官 ...

  6. C_数据结构_递归不同函数间调用

    # include <stdio.h> void f(); void g(); void k(); void f() { printf("FFFF\n"); g(); ...

  7. BugPhobia发布篇章:学霸在线系统测试报告

    0x00 :测试报告版本管理 版本号 具体细节 修订时间 V 1.0 整理第一轮迭代用户管理和登陆注册的功能性验证测试,预计将继续网页对浏览器版本的兼容性测试 2015/11/12 V1.0.1 整理 ...

  8. synchronized关键字的学习与总结

  9. SpringMVC视图解析器概述

    不论控制器返回一个String,ModelAndView,View都会转换为ModelAndView对象,由视图解析器解析视图,然后,进行页面的跳转. 控制器处理方法---->ModelAndV ...

  10. 第五届蓝桥杯C++B组 地宫取宝

    代码: #include <bits/stdc++.h> using namespace std; #define ll long long const ll mod = 1e9 + 7; ...