牛客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=lr​ai​。

现在智乃有一个长度大小为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的方程就可以写出来了

\[f[j][m]=\displaystyle\max_{abs(j-i)\leq m\leq k}(f[j-1][m-abs(i-j)]+a[i])\ \ \ \ ( i+k\geq j\geq max( i-k)) \]

这个式子的含义就是对每一个数字\(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.这个第一个贪心我没有仔细去思考。这题在我这边被划分进了不值得花时间考虑的题目。这次的事情告诉我是不对的。

事实上这么多题目我很多都是这个原因。
这种情况甚至能让我普及组难度的题目都写不出来。
有些时候就是要敢去想。这是很重要的能力。
既然我做了这么多事情,其实就是要去承担开题人的身份的,那这样一道普及到提高的题目都能这么麻烦,肯定是不行的。

posted @ 2024-02-12 11:02  HL_ZZP  阅读(6)  评论(0编辑  收藏  举报