最大子段和之M子段和
最大M
子段和
题目模型
N
个整数组成的序列 \(a_1,a_2,a_3,…,a_n\) ,将这N
个数划分为互不相交的M
个子段,并且这M
个子段的和是最大的。
问题分析
方法一:
看到序列,我们首先要尝试用线性
dp
去处理,线性dp
经典状态定义:f[i][j]
,i
一般表示序列的前i
个元素,j
表示限制,这里表示划分了j
个不相交的子段,我们还需要对i
进行进一步的定义,即是否包含第i
项,因为对当前元素a[i]
来说,要么单独成一个子段,要么和最后一个子段合并,所以必须包含第i
个元素。动态转移方程:
dp[i][j]=max(dp[i-1][j],dp[k][j-1])+a[i] (j-1<=k<i)
。Code
#include <bits/stdc++.h>
const int maxn = 1e3+3,Inf=0x3f3f3f3f;
typedef long long LL;
int a[maxn],dp[maxn][maxn];
void Solve(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i){//前i个元素
for(int j=1;j<=std::min(i,m);++j){//划分出j个子段
if(i==j)dp[i][j]=dp[i-1][j-1]+a[i];//显然
else{
int temp=dp[i-1][j];//把a[i]直接并到最后一子段
for(int k=j-1;k<i;++k)//枚举上一个状态的最后一个子段的右端点,a[i]单独作为一个子段
temp=std::max(temp,dp[k][j-1]);
dp[i][j]=temp+a[i];
}
}
}
int ans=-Inf;
for(int i=m;i<=n;++i)
ans=std::max(ans,dp[i][m]);
printf("%d\n",ans);
}
int main(){
Solve();
return 0;
}
时间效率为:\(O(n^3)\) ,空间效率为:\(O(m*n)\)。
方法二:
我们尝试对方法一的
dp
阶段和状态进行修改, 即把子段限制数M
作为阶段,即状态dp[i][j]
表示把序列前j
分成i
个子段且包含a[j]
的最大子段和。动态转移方程有:
dp[i][j]=max(dp[i][j-1],dp[i-1][k])+a[j] (i-1<=k<j)
。dp[i][j-1]+a[i]
:表示合并到最后一个子段里dp[i-1][k]+a[i]
:表示前k
元素挑出k
个子段,所以k>=j-1
,然后a[i]
单独的子段。此动态转移方程同样满足无后效性和最优子结构。
我们把问题的所有状态记录下来形成一个二维矩阵,显然当前状态只跟它上一行和左边的状态有关,我们可以把空间效率压掉以为变成 \(O(n)\) 。
同时上一行的状态只有在当前状态前面的最大值对转移有用,我们可以在遍历当前行时维护一下上一行的最大值,这样时间效率就压掉了一个
n
,变成\(O(n*m)\)。Code
#include <bits/stdc++.h>
typedef long long LL;
const int maxn = 1e4+5;
const LL Inf=0x3f3f3f3f3f3f3f3f;
LL a[maxn],dp[2][maxn];
void Solve(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%lld",&a[i]);
int k=1;//滚动数组指针,k表示当前行,!k表示上一行
for(int i=1;i<=m;++i,k=!k){//枚举区间个数
LL Max=-Inf;
for(int j=i;j<=n;j++){
Max=std::max(Max,dp[!k][j-1]);//记录前j-1,分成i-1个区间时最大值
if(i==j)
dp[k][j]=dp[!k][j-1]+a[j];
else//要么是a[j]单独成一个区间,此时为Max+a[j],或者直接合并为dp[k][j-1]+a[j]
dp[k][j]=std::max(Max,dp[k][j-1])+a[j];
}
} LL ans=-Inf;
for(int i=m;i<=n;++i)//!k行才记录的是第m行的状态
ans=std::max(ans,dp[!k][i]);
printf("%lld\n",ans);
}
int main(){
Solve();
return 0;
}
方法三
方法二把空间优化到线性,时间优化到\(O(n^2)\) ,但如果 \(n\) 和 \(m\) 高达 \(10^5\)显然
dp
是无法解决了。比如:51nod 1115 最大M子段和 V3对这个问题我们先对原数组进行处理,然后利用可撤销贪心解决。
首先把原数组连续的正数加起来变成一个数,连续的负数加起来变成一个数,
0
加到哪里都一样,这样我们就得到了一个正负交替的环形序列。如果新的环形序列里正数的个数为
cnt
,所有正数之和为ans
则存在两种情况:- \(cnt<=M\),因为可以选空,所以答案就是所有正数之和
ans
。 - \(cnt>M\) ,此时
ans
包含了cnt
个正数区间,所以我们需要通过操作减少cnt-M
个区间。
- \(cnt<=M\),因为可以选空,所以答案就是所有正数之和
对于情况
2
,我们可以通过两种操作减少区间:- 删除一个正数(
ans
包含所有正数之和,删除一个正数相当于减少了一个区间). - 将一个负数与它两边的正数合并,相当于把两个正数区间合并成了一个区间,也减少了一个区间。
- 删除一个正数(
很容易能想到一个贪心思想,即每次选择最小的正数删除一个区间,或选择最大一个负数与它两边的正数合并减少一个区间。
但这个贪心是不正确的,因为删除一个较小的正数是在减少一个区间的最优,但有可能不删除这个正数,而是通过若干次合并在减少多个区间。
- 例如:序列
10 -4 3 -4 8 -100
其中M=1
,根据刚才的贪心策略,我们先删除3
,减少了一个区间,然后最大的负数为-4
,此时因为3
已经被删除-4
就无法合并了。为了解决类似的问题,我们要用到可撤销的贪心思想。
- 例如:序列
实现步骤:
- 把序列中正数均变成负数,并把数都压入大根堆。
- 套用可撤销贪心思想,做
cnt-M
次即可,具体见代码。
Code
#include <bits/stdc++.h>
typedef long long LL;
const int maxn=2e5+5;
struct Node{
int id;
LL w;
Node(){};
Node(int x,LL y){id=x;w=y;}
bool operator <(const Node &a)const{
return w<a.w;
}
};
int n,m,N=0,L[maxn],R[maxn];
LL ans=0,a[maxn],b[maxn];
std::priority_queue<Node> q;
bool flag[maxn];
void Init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);//原始数组
if(!N || (a[i]>=0)!=(b[N]>=0))
b[++N]=a[i];//新数组,上个数和当前数符号不一样节点++
else
b[N]+=a[i];//符号一样,累加
}
if((b[1]>=0)==(b[N]>=0))//因为是环,b[1]和b[N]同号就合并
b[1]+=b[N--];//合并后新数组个数减一,记得N--
}
void Solve(){
Init();
int tot=0;//记录正数个数
for(int i=1;i<=N;++i){//遍历新数组,压入大根堆
L[i]=i-1; R[i]=i+1;//初始化i的左右邻居
if(b[i]>0){//正数累加到答案,然后变负
ans+=b[i]; b[i]=-b[i]; tot++;
}
q.push(Node(i,b[i]));
}
R[N]=1; L[1]=N;//注意是环形
if(m>=tot){//正数个数小于m,则全部选
printf("%lld\n",ans);return;
}
m=tot-m;//正数大于m则合并或删除tot-m个区间
while(m--){
Node t=q.top(); q.pop();
int i=t.id;
if(flag[i]){++m; continue;}
else{//可撤销贪心
ans+=b[i];
flag[L[i]]=1;
flag[R[i]]=1;
b[i]=b[L[i]]+b[R[i]]-b[i];
R[L[L[i]]]=i;
L[R[R[i]]]=i;
q.push(Node(i,b[i]));//压入新点
L[i]=L[L[i]];
R[i]=R[R[i]];
}
}
printf("%lld\n",std::max(0LL,ans));
}
int main(){
Solve();
return 0;
}
最大子段和之M子段和的更多相关文章
- (最大连续和/最大子段和) P1115 最大子段和 洛谷
题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大. 输入输出格式 输入格式: 第一行是一个正整数NN,表示了序列的长度. 第二行包含NN个绝对值不大于1000010000的整数A_iAi ...
- XCOJ 1103 (LCA+树链最大子段和)
题目链接: http://xcacm.hfut.edu.cn/problem.php?id=1103 题目大意:链更新.链查询,求树链的最大子段和.(子段可以为空) 解题思路: 将所有Query离线存 ...
- hdu1003 dp(最大子段和)
题意:给出一列数,求其中的最大子段和以及该子段的开头和结尾位置. 因为刚学过DP没几天,所以还会这题,我开了一个 dp[100002][2],其中 dp[i][0] 记录以 i 为结尾的最大子段的和, ...
- HDOJ-1003 Max Sum(最大连续子段 动态规划)
http://acm.hdu.edu.cn/showproblem.php?pid=1003 给出一个包含n个数字的序列{a1,a2,..,ai,..,an},-1000<=ai<=100 ...
- 最大子段和(c++)
// 最大子段和.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> using namesp ...
- 51Node 1065----最小正子段和
51Node 1065----最小正子段和 N个整数组成的序列a[1],a[2],a[3],…,a[n],从中选出一个子序列(a[i],a[i+1],…a[j]),使这个子序列的和>0,并且这 ...
- 最大M子段和 V2
51nod1053 这题还是我们熟悉的M子段和,只不过N,M<=50000. 这题似乎是一个堆+链表的题目啊 开始考虑把所有正数负数锁在一起. 比如: 1 2 3 -1 –2 -3 666 缩成 ...
- 51nod 循环数组最大子段和
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1050 对于普通的数组,只要求一次最大子段和即可.但是这题是可以循环的,所 ...
- [日常训练]最大M子段和
Description 在长度为的序列中选出段互不相交的子段,求最大字段和. Input 第一行两个整数. 第二行个整数. Output 一行一个整数表示最大值. Sample Input 5 2 1 ...
随机推荐
- Vue 事件的高级使用方法
Vue 事件的高级使用方法 事件方法 在Vue中提供了4中事件监听方法,分别是: $on(event: string | Array, fn) $emit(event: string) $once(e ...
- html基础:DOM操作
DOM(Document Object Model 文档对象模型) 一个web页面被封装成一个dom对象,通过dom中的js对页面的标签进行操作 一.获取对象 浏览器页面上右键--检查--consol ...
- Solaris – configure ftp server
SUN Solaris FTPSUN Solaris comes with ftp daemon based on WU-FTPd Washington University project.Whil ...
- [Java并发编程之美]第1章 线程基础 补充知识
1.2线程创建与运行 创建线程有三种方式: 继承Thread类并重写run方法: 实现Runnable接口的run方法,new Thread时将该类对象作为参数传入: 实现Callable接口的cal ...
- x86-TSO : 适用于x86体系架构并发编程的内存模型
Abstract : 如今大数据,云计算,分布式系统等对算力要求高的方向如火如荼.提升计算机算力的一个低成本方法是增加CPU核心,而不是提高单个硬件工作效率. 这就要求软件开发者们能准确,熟悉地运用高 ...
- Halcon使用骨架法处理激光条并拟合直线
dev_close_window () * 设置颜色 dev_set_color ('green') * 读取图像 read_image (Image, 'images3/1') * 获得图像尺寸 g ...
- Oracle学习(六)存储过程
一.简介 1.定义 所谓存储过程,就是一段存储在数据库中执行某块业务功能的程序模块. 它是由一段或者多段的PL/SQL代码块或者SQL语句组成的一系列代码块. 2.结构分析 create [or re ...
- 海量数据处理之布隆过滤器BloomFilter算法
Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法.通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合.使用场景:数据量为100亿 ...
- Python3.8下载安装步骤及环境变量配置详解
安装地址:https://www.python.org/ 打开python官网网址,点击 Python 3.8.5 3.下载与自己电脑系统相匹配的版本(这里以64为例) 点击下载完成后打开文件运行 点 ...
- 使用redis来调用iptables,封禁恶意IP
话不多说,通常大多数站点都会有被薅羊毛的情况,防护无非也就是业务层做处理,短时内不再响应恶意请求啦.虽然不响应了,可还是会消耗资源的,比如我要从数据库(当然也可能是内存数据库)去查询下,你是不是恶意的 ...