优化----dp(性质分析)

我写的题解一般都很长,可能因为我太菜了QAQ,所以dalao们不要看它长就觉得它是毒瘤。。。

Description

​ 现在有一个长度为 的整数序列 ,你需要从中选出 k k 个不相交的连续子区间(可以存在元素不被选),从
左到右记它们的和为 s 1 , s 2 s k s_1,s_2……s_k . 我们的优化目标是最大化下述和式:

i = 1 k 1 s i s i + 1 \sum_{i=1}^{k-1} {|s_i-s_{i+1}|}

你只需要输出这个最大的和即可

Solution

据我判断本道题的k个子区间是不能为空的。。。

如果可以为空的话,应该不存在有数不被选取的情况

理由:假设最优答案中两个被选取的区间,和分别为x,y(假设x<y),它们之间有一段区间的数没有选,那么如果这一段数的和为正,加到y中会更优,反之加到x会更优,所以矛盾啦。然后这样的话,貌似就变简单了??反正不是题解那样的了。(我为什么要说这个啊)

可能算个技巧??对于处理绝对值的问题可以拆一下

x y = m a x ( x y , y x ) |x-y|=max( x-y, y-x )

其实我觉得这句话没有什么用,不会刻意去拆,但是一旦涉及就想到了。

重点在于考虑选取的第i个连续区间产生的贡献,显然它的贡献只与它和它前一个区间以及后一个区间的大小关系有关,分情况讨论一下

s i 1 &gt; s i &gt; s i + 1 s i 1 s i + s i s i + 1 = s i + 1 s i s_{i-1}&gt;s_i&gt;s_{i+1}\Rightarrow s_{i-1}-s_i+s_i-s_{i+1}=s_{i+1}-s_i

这种情况对于 s i s_i 来讲它没有贡献即贡献系数为0,对于这种情况,我们称 s i s_i 正处于下降状态

s i 1 &gt; s i &lt; s i + 1 s i 1 s i + s i + 1 s i = s i 1 + s i + 1 2 s i s_{i-1}&gt;s_i&lt;s_{i+1}\Rightarrow s_{i-1}-s_i+s_{i+1}-s_i=s_{i-1}+s_{i+1}-2\ast s_i

s i s_i 被减了两次,它的贡献系数为-2,我们称 s i s_i 处于谷的状态

s i 1 &lt; s i &lt; s i + 1 s_{i-1}&lt;s_i&lt;s_{i+1}

这与第一种类似贡献系数为0,不赘述啦,我们称它处于上升状态

s i 1 &lt; s i &gt; s i + 1 s_{i-1}&lt;s_i&gt;s_{i+1}

与第二种类似贡献系数为+2,我们称它处于峰的状态

当确定第i个子区间的状态后,它变的相对独立,不会再受前后的约束了,就变的好处理许多

于是我们每次枚举第i个区间的状态,从前一个区间的相应状态转移。

说一下状态的转移(:前面是当前,后面是可以转移到它的前一个区间的状态)

峰:上升

谷:下降

上升:上升,谷

下降:下降,峰

注意一下这里上升和下降分别能从谷和峰的状态转移,对于一个谷,它也可看做是一个上升的状态,峰也是同理,所以这两个转移直接从同层转移(就是第i个子区间处于谷的状态可以转移到第i个子区间处于上升的状态)

对于i=1或者i=k的情况要特殊处理

然后就可以愉快的做dp啦。

f[ i ] [ j ] [ h ]表示前i个数选取j个子区间第j个子区间状态为h的方案

然后暴力枚举下一个选取的子区间转移即可,时间复杂度O( n 4 k n^4k

(此段比较废)也许你会想(好吧是我想),如果不记录子区间的和,对于第i个子区间它实际上比前面的区间要小,但是我枚举状态的时候将它当做峰的状态,如果这个区间和为正数,这不就错了吗? 的确过程中会出现一些不合法的状态,但就例如所举的这种例子,考虑最优的方案中它应该是峰还是谷,假如最优方案中它是峰,那要么是这个区间选的太小,或者是前一个区间选的太大,这就达不到最优的方案了,所以最优解仍然存在,这个不优的解就被刷了,假如最优的方案中它是谷。那考虑当时为什么选择让它当谷而不是峰,说明让它当谷能够使后面的集合有更有的选择,使得答案最优,如果要它当峰,它完全可以更加大,所以这样选不能使答案最优,最优解任然存在。

考虑优化。

(不考虑开头和结尾的子区间)对于一个谷和一个峰之间,会出现若干处于上升状态的子区间,这些子区间是没有贡献的。回想之前关于子区间不能为空的分析,事实上把所有数都选取到为峰或者谷的子区间内会达到最优,但是这样做可能会导致选择的子区间个数达不到k,为了满足这一限制,我们在一个谷和一个峰之间选择了一些子区间,这些子区间没有贡献,以到达k个子区间。这样来说的话,仍可以不存在数不被选择的情况,如果它处在一个峰和一个谷之间,它肯定要选比较好。如果它处在一个没有贡献的子区间旁(上升或下降),那么完全可以将它选入这个子区间内,反正也不会有贡献。

所以对于每一个数,只要考虑它是加入一个已经存在的子区间还是新开一个子区间即可。

代码

#include <bits/stdc++.h>
using namespace std;
int n,k,f[30010][210][4];
//0:峰 1:上升 2:谷 3:下降 
int main()
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
	scanf("%d%d",&n,&k);	
	for(int i=1;i<=n;++i)
	  for(int j=1;j<=k;++j)
	    f[i][j][0]=f[i][j][1]=f[i][j][2]=f[i][j][3]=-0x3f3f3f3f; 
	f[0][1][0]=f[0][1][1]=f[0][1][2]=f[0][1][3]=-0x3f3f3f3f;
	for (int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		for (int j=1;j<=min(i,k);j++)
		  if (j==1||j==k)
		  {
		  	f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][1])+x;
		  	f[i][j][2]=max(f[i-1][j][2],f[i-1][j-1][3])-x;
		  	f[i][j][1]=max(f[i-1][j][1],f[i][j][2]);
			f[i][j][3]=max(f[i-1][j][3],f[i][j][0]);
	      }
	      else
	      {
	      	f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][1])+x+x;
	      	f[i][j][2]=max(f[i-1][j][2],f[i-1][j-1][3])-x-x;
	      	f[i][j][1]=max(f[i][j][2],f[i-1][j][1]);
	      	f[i][j][1]=max(f[i][j][1],f[i-1][j-1][1]);
	      	f[i][j][3]=max(f[i][j][0],f[i-1][j][3]);
	      	f[i][j][3]=max(f[i][j][3],f[i-1][j-1][3]);
	      }
	}
	cout<<max(f[n][k][1],f[n][k][3]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/yu25_21_5/article/details/100088936
今日推荐