牛客2.7比赛E,F题
链接:https://ac.nowcoder.com/acm/contest/67743/E
来源:牛客网
智乃最近学习了冒泡排序和最长子段和,所以她现在想把它们合并成一个新的算法。
众所周知,冒泡排序是一种通过交换相邻元素实现排序的算法,最长子段和是指从一个数组aaa中取出一段连续的非空数组区间[l,r][l,r][l,r],最大化数组区间的和∑i=lrai\sum_{i=l}^{r}a_{i}∑i=lrai。
现在智乃有一个长度大小为NNN的数组,数组中元素的值有正有负。她想要先进行恰好KKK次相邻元素的交换操作,再求整个数组的最大子段和。她想要让最后求出的最大子段和尽可能的大,请你帮助智乃算出她最终可能的最大子段和有多大。
输入描述:
第一行输入两个正整数N,K(2≤N≤103,0≤K≤100)N,K(2\leq N \leq 10^3,0 \leq K \leq 100)N,K(2≤N≤103,0≤K≤100)表示数组的长度,交换的次数。
接下来一行NNN个整数,输入数组元素ai(−109≤ai≤109)a_i(-10^{9} \leq a_{i} \leq 10^{9})ai(−109≤ai≤109)表示数组元素的值。
输出描述:
仅一行一个整数,表示交换后能取到的非空最大子段和是多少。
示例1
输入
[复制](javascript:void(0)😉
5 0
-1000000000 -1000000000 -1000000000 -1000000000 -1000000000
输出
[复制](javascript:void(0)😉
-1000000000
说明
注意“非空”
示例2
输入
[复制](javascript:void(0)😉
5 1
5 4 -100 2 3
输出
[复制](javascript:void(0)😉
11
说明
交换"-100"和 "2"
示例3
输入
5 7
5 4 -100 2 3
输出
14
说明
交换7次恰好可以将其排序成[−100,2,3,4,5][-100,2,3,4,5][−100,2,3,4,5]
示例4
输入
2 100
1 -1
输出
1
说明
可以反复交换"1"和"-1"。
备注:
注意已经通过一次的题目不再次计算罚时,你可以活用这一规则减少自己的罚时次数。
写起来很简单的dp解法。
\(f[i][j]\)表示当前\(i\)的位置被放了不要的数字,已经用了\(j\)次交换的机会时的最大子串和,还有一个隐藏的维度k,是最外层的循环,表示当前在给\(a[k]\)做决策,决定\(a[k]\)是放在其他位置还是留下计入答案。
有点类似背包,但是又不太一样,一样的地方在于这个模型也有“代价”,就是交换次数,但是这个交换次数又是由当前的状态决定的,也就是会受到前面决策的影响。那排除这种后效性的简单的方法就是再加一维状态(如果加不上要么是思路不够开阔要么就是这题根本不能dp),我们加上一维状态,表示前面已经有\(j\)个数字被抛弃了,也就是放在了两边,这也帮助我们决定了后面的数字的移动需要多少代价。这样后效性就排除了。
其实这个\(j\)表诉不正确。但是可以先这么理解一下。
这个\(j\)表示的是,上一个被抛弃的数字的位置在\(j\)。
这样看,其实dp的方程就可以写出来了
这个式子的含义就是对每一个数字\(a[i]\),枚举把它放到哪里能够使答案最大。
而不取它的情况则是由其他的循环考虑,我们的最终答案是\(f\)数组里面最大的数字。
但是我还有一个疑点。为什么把\(a[i]\)移动之后就一定保证这个地方是最终的最大子串和的一部分呢?
这个状态里面并没有钦定某一块为最大子段和的部分。。还是说我没理解出来?
卧槽,我明白了,这个\(j\),上面说错了,其实是指当前的最大子串和的结束的位置,所以这个\(j\)的范围才是这样,所以这个转移才是从\(j-1\)到\(j\)的。
那我没有问题了。。
先写完代码吧
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
char c=getchar();ll a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n,k,a[1001],f[1001][1001],ans=-1e9;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();k=read();
for(ll i=1;i<=n;i++)
{
a[i]=read();
ans=max(ans,a[i]);
}
if(ans<=0)
{
cout<<ans<<endl;
return 0;
}
for(ll i=1;i<=n;i++)
{
for(ll j=min(n,i+k);j>=max(1LL,i-k);j--)
{
for(ll h=abs(i-j);h<=k;h++)
{
f[j][h]=max(f[j][h],f[j-1][h-abs(j-i)]+a[i]);
ans=max(ans,f[j][h]);
}
}
}
cout<<ans<<endl;
return 0;
}
这个dp。。感觉不难啊,但是我又又又没做出来。
这次的原因是啥
1.没时间吧。考试的时候心态很tm爆炸,题目写一题wa一题。真的好难受,而且,算法都是对的啊啊啊啊啊啊
2.这个第一个贪心我没有仔细去思考。这题在我这边被划分进了不值得花时间考虑的题目。这次的事情告诉我是不对的。
事实上这么多题目我很多都是这个原因。
这种情况甚至能让我普及组难度的题目都写不出来。
有些时候就是要敢去想。这是很重要的能力。
既然我做了这么多事情,其实就是要去承担开题人的身份的,那这样一道普及到提高的题目都能这么麻烦,肯定是不行的。