51Nod 最大M子段和系列 V1 V2 V3
前言
\(HE\)沾\(BJ\)的光成功滚回家里了...这堆最大子段和的题抠了半天,然而各位\(dalao\)们都已经去做概率了...先%为敬。
引流之主:老姚的博客
最大M子段和 V1
思路
- 最简单的ver.数据范围在5000以内,可以考虑暴力一点的做法\(O(n^3)\),定义\(dp\)状态\(dp[i][j]\),递推式子:
\]
- 其中\(i\)表示序列中前\(i\)个元素且包含第\(i\)个元素,而\(j\)则表示划分了\(j\)个不同的字段。而对于\(a[i]\)来说,要么和最后一个字段合并,要么单独成为一个字段,所以就可以得出动态转移方程。
- 此时我们可以看出,对于第二维\(j\),只与\(j\)和\(j-1\)有关,所以我们可以互换,使\(dp[i][j]\)表示把序列前\(j\)个元素分成\(i\)个子段且包含\(a[j]\)的最大子段和。此时的状态就只和上一行和左边的状态有关了,所以我们就可以把效率压到\(O(m*n)\)。
此时的动态转移方程:
\]
- 还有一个要特判的就是\(i=j\)的时候,见代码即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const ll INF=1e15;
ll a[maxn],dp[2][maxn];
int n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
int k=1;
for(int i=1;i<=m;i++,k=k^1){
ll temp=-INF;
for(int j=i;j<=n;j++){
temp=max(temp,dp[k^1][j-1]);
if(i==j)
dp[k][j]=dp[k^1][j-1]+a[j];
else
dp[k][j]=max(temp,dp[k][j-1])+a[j];
}
}
ll ans=-INF;
for(int i=m;i<=n;i++)
ans=max(ans,dp[k^1][i]);//注意此处是k^1记录的才是第m行的状态
printf("%lld\n",ans);
return 0;
}
最大M子段和 V3
思路
- 为什么先说V3因为V3是环形的边界处理比V2还要简单。对于V1的DP虽然效率是\(O(m*n)\)但是显然从V2开始就已经有m接近于n的数据了,所以最差的效率为\(O(n^2)\),所以DP显然是不行了。这时候用可撤销贪心。
- 可撤销贪心简单来说就是贪心时候的悔棋操作,可能最一开始选择的为最大的贡献\(w[i]\),但是第二轮选择可能会发现选择\(w[i-1]\)和\(w[i+1]\)更优,那么此时的答案就应加上\(w[i-1]+w[i+1]-w[i]\),相当于建立了一个新的节点,同时其相邻关系也会发生变化。
- 对于此题,我们可以把相邻的相同符号的数都加在一起(0加在哪里都一样甚至可以选择直接把0删了),这样就得到了一个正负交替的环形序列,而每个数均代表一个区间,此时设序列中正数个数为\(cnt\),正数的和为\(ans\)。
- 若\(cnt\le m\),则依照题目要求直接输出\(ans\)。
- 否则需要删减\(cnt-m\)个区间。
- 考虑如何删减:
- 可以选择直接删除一个正数。则每次删除最小的正数。
- 也可以将一个负数和相邻两个正数合并,即将两个正数区间合并成一个。这时选择最大的负数和两边合并。
- 但是此时不能随便删除,因为可能通过多次合并减少区间得到的答案会更优,盗老姚的数据:
10 -4 3 -4 8 -100
- 若先删除最小的正数3,则第二步要选择最大的负数-4,但是此时的3已经被删除了无法合并,因此我们要用到可撤销贪心思想。
- 实现方法:
- 将修改后的序列中的正数改为负数,压入大根堆(网上好多题解都说用绝对值,但实际上正变负更方便一点,如果用pair的话都不用重载运算符了)。
- 可撤销贪心执行\(cnt-m\)次。
一个不怕死的HACK
- 老姚的代码
#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;
}
- 在51Nod直接AC了,不得不说51Nod真的都是随机数据。
- 有个很明显的错误就是如果给出的序列都是一个符号,例如下面这个HACK数据:
Input:
5 4
1 1 1 1 1
Output:
5- 然而老姚的代码会输出0,问题在这行:
if((b[1]>=0)==(b[N]>=0))
b[1]+=b[N--];
- 如果全为一个符号,最后\(N=1\),显然\(b[1]=b[N]\),这样会导致\(N\)变成\(0\)。如果对于全负,答案必输出0没什么影响,但是全正的话就不行了。不过也很好弄,加个特判即可。具体见代码(把老姚的代码改了改增加可读性(雾))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
struct Node{
int id;
ll w;
Node(){};
Node(int x,ll y){
id=x;w=y;
}
friend bool operator <(const Node &a,const Node &b){
return a.w<b.w;
}
};
int n,m,N;
int L[maxn],R[maxn];
ll ans;
ll a[maxn],b[maxn];
bool vis[maxn],flag=1;
priority_queue<Node> q;
void Init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(a[i]<0)flag=0;
else ans+=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--];
}
int main(){
Init();
int tot=0;
for(int i=1;i<=N;i++){
L[i]=i-1;R[i]=i+1;
if(b[i]>0){
b[i]=-b[i];
tot++;
}
q.push(Node(i,b[i]));
}
R[N]=1;L[1]=N;
if(m>=tot||flag){
printf("%lld",ans);
return 0;
}
m=tot-m;
while(m--){
Node t=q.top();
q.pop();
int i=t.id;
if(vis[i]){
m++;
continue;
}else{
ans+=b[i];
q.push(Node(i,b[i]));
vis[L[i]]=1;vis[R[i]]=1;
b[i]=b[L[i]]+b[R[i]]-b[i];
R[L[L[i]]]=i;
L[R[R[i]]]=i;
L[i]=L[L[i]];
R[i]=R[R[i]];
}
}
printf("%lld",max(ans,0LL));
return 0;
}
最大M子段和 V2
- 最后就是V2了,一开始尝试特判\(0\)和\(N\)处的边界但发现巨难写,后来发现其实只需要加上一个权值为负无穷的\(0\)号节点就行了...
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+10;
struct Node{
int id;
ll w;
Node(){};
Node(int x,ll y){
id=x;w=y;
}
friend bool operator <(const Node &a,const Node &b){
return a.w<b.w;
}
};
int n,m,N;
int L[maxn],R[maxn];
ll ans;
ll a[maxn],b[maxn];
bool vis[maxn],flag=1;
priority_queue<Node> q;
void Init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
if(a[i]<0)flag=0;
else ans+=a[i];
if(!N && a[i]<0)continue;
if(!N || (a[i]>=0)!=(b[N]>=0))
b[++N]=a[i];
else
b[N]+=a[i];
}
while(b[N]<0)b[N--]=0;
}
int main(){
Init();
int tot=0;
for(int i=1;i<=N;i++){
L[i]=i-1;R[i]=i+1;
if(b[i]>0){
b[i]=-b[i];
tot++;
}
q.push(Node(i,b[i]));
}
R[N]=0;L[0]=N;R[0]=1;
q.push(Node(0,b[0]=-1e15));
if(m>=tot||flag){
printf("%lld",ans);
return 0;
}
m=tot-m;
while(m--){
Node t=q.top();
q.pop();
int i=t.id;
if(vis[i]){
m++;
continue;
}else{
ans+=b[i];
q.push(Node(i,b[i]));
vis[L[i]]=1;vis[R[i]]=1;
b[i]=b[L[i]]+b[R[i]]-b[i];
R[L[L[i]]]=i;
L[R[R[i]]]=i;
L[i]=L[L[i]];
R[i]=R[R[i]];
}
}
printf("%lld",max(ans,0LL));
return 0;
}
51Nod 最大M子段和系列 V1 V2 V3的更多相关文章
- android google map v1 v2 v3 参考
V1,V2已经不被推荐使用,谷歌强烈推荐使用V3. 本人在选择时着实纠结了良久,现在总结如下: 对于V1,现在已经申请不到API KEY了,所以不要使用这个版本.这个是网址:https://devel ...
- 51nod 最大M子段和系列
1052 最大M子段和 N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的.如果M >= N个数中正数的个数,那么输出所 ...
- 51nod Bash游戏(V1,V2,V3,V4(斐波那契博弈))
Bash游戏V1 有一堆石子共同拥有N个. A B两个人轮流拿.A先拿.每次最少拿1颗.最多拿K颗.拿到最后1颗石子的人获胜.如果A B都很聪明,拿石子的过程中不会出现失误.给出N和K,问最后谁能赢得 ...
- 51nod 最大M子段和系列(1052、1053、1115)
51nod1052 数据量小,可使用O(N*M)的DPAC,递推公式: dp[i][j]=max(dp[i-1][j-1], dp[i][j-1])+a[j]; dp[i][j]表示前j个数取 i 段 ...
- 51Nod 最大公约数之和V1,V2,V3;最小公倍数之和V1,V2,V3
1040 最大公约数之和 给出一个n,求1-n这n个数,同n的最大公约数的和.比如:n = 6 1,2,3,4,5,6 同6的最大公约数分别为1,2,3,2,1,6,加在一起 = 15 输入 1个数N ...
- GoogLeNet 之 Inception v1 v2 v3 v4
论文地址 Inception V1 :Going Deeper with Convolutions Inception-v2 :Batch Normalization: Accelerating De ...
- 从Inception v1,v2,v3,v4,RexNeXt到Xception再到MobileNets,ShuffleNet,MobileNetV2
from:https://blog.csdn.net/qq_14845119/article/details/73648100 Inception v1的网络,主要提出了Inceptionmodule ...
- 51nod 1254 最大子段和 V2 ——单调栈
N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的.当所给的整数均为负数时和为 ...
- 51nod 1254 最大子段和 V2
N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的.当所给的整数均为负数时和为 ...
随机推荐
- oracle之分层查询
ORACLE分层查询start with&connect by 分层查询是select 语句的扩展,目的是迅速找出表中列-列的隶属关系. 19.1 树的遍历 ORACLE是一个关系数据库管理系 ...
- Fiddler的基本界面介绍
前言 fiddler的界面,主要是分以下6个模块. 一:主菜单栏: 可以参考:https://www.cnblogs.com/sjl179947253/p/7620524.html 二:工具栏: 1. ...
- 原文https://blog.csdn.net/hongzhen91/article/details/57422897
C语言操作EXCEL文件(读写) 大小宝 2017-02-26 18:18:37 94247 收藏 136展开C语言操作EXCEL文件(读写)本文主要介绍通过纯C语言进行EXCEL的读写操作:(修改时 ...
- 云计算openstack共享组件——消息队列rabbitmq(3)
一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们. 消息传 ...
- 第3章 02 python字符串类型及操作
字符串切片 取字符串从结尾到开头,相当于字符串逆序 转义符 转义符表达特定字符的本意 转义符 \“ ----> ” 字符串操作符 例子: 获取星期字符串 定义星期 获取用户 ...
- jpeg编解码概述
本博文为概览性介绍.后面有空了再分几篇博文分别介绍所用到的技术细节. 1.编解码目标 编码和解码是个逆过程.jpeg编码的目的在于图形去冗余,进行数据压缩,解码的目的在于还原图像,使能够进行预览. 2 ...
- 解决npm被墙的问题
npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以我们需要解决这个问题. http://npm.taobao.org/ 淘宝的开发团队把npm在国内做了一个备份. 安装淘宝的cnpm np ...
- p.array 的shape (2,)与(2,1)的分别是什么意思
numpy.ndarray.shap是返回一个数组维度的元组. (2,)与(2,1)的区别如下: ndarray.shape:数组的维度.为一个表示数组在每个维度上大小的整数元组.例如二维数组中, ...
- 概率派VS贝叶斯派
机器学习中的MLE和MAP两大学派的争论: 频率学派 - Frequentist - Maximum Likelihood Estimation (MLE,最大似然估计): 频率学派认为世界是确定的, ...
- 使用Maven那么久了,你对企业级Maven的核心配置了解多少?
写在前面 相信从事Java工作的小伙伴们多多少少都会接触到Maven.使用Maven来搭建项目,能够极大的方便我们构建项目的依赖关系,对于项目中需要依赖的Jar包,也只是简单的在pom.xml中进行配 ...