洛谷1846 游戏 dp

题目链接

题意:
给定两个正整数数列,你要用它们来做一个游戏:你需要对数列进行若干次操作,每一次操作,应选择两个正整数K1和K2 ,并删除第一个数列的最后K1个数,计算出它们的和S1;删除第二个数列的最后K2个数,计算出它们的和S2。这一次操作的得分就是(S2-K2 )*(S1-K1 )。两个数列应同时被清空,不允许一个数列空了,而另一个数列中还有数。游戏的总得分就是每一次操作的得分总和。

求最小的总得分。

数列长度 < = 2000 <=2000 <=2000,权值都是正整数。

题解:
这种题看起来评分不高,但是其实也不一定就很好做。一般代码很短的题都会思维难度不低。

这个题最简单的dp想法是 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第一个数列考虑了后 i i i个数,第二个数列考虑了后 j j j个数的最小答案。最暴力的转移方法是枚举两个数列的上一个位置分别在哪里,这样可以得到一个 O ( n 2 ∗ m 2 ) O(n^2*m^2) O(n2m2)的做法。但是这个做法似乎也不太能用一些常规的手段来优化。

但是这个题的正权做法并不是更换状态,而是更换转移思路。首先对于贡献答案的式子中减去的那个区间长度,我们转化成在两个数列里的每个数的权值都减去 1 1 1,这样根据乘法的分配律,相乘之后结果不会改变的。这样之后我们不去枚举两个数列上一次的端点在哪里了,而是直接从前面的位置转移过来。我们考虑两次合并,设第一次的贡献是 a 1 ∗ b 1 + a 2 ∗ b 2 a1*b1+a2*b2 a1b1+a2b2,那么如果两次合为一次的话贡献是 ( a 1 + a 2 ) ∗ ( b 1 + b 2 ) (a1+a2)*(b1+b2) (a1+a2)(b1+b2),后者一定大于前者,所以我们分的段数越多答案越优。由于两端长度不一定相同,所以我们每次一定是在一个数列选了一个数,再另一个数列选了连续的几个数。那么我们就在转移的时候考虑当前是在两个数列都是一个数,还是第一个数列仍然用之前的数,第二个数列继续加进新的数,或者第一个数列继续加进来新的数,第二个数列仍然是只选之前的那一个数。于是我们得出dp方程式: d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j − 1 ] , d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) + a [ i ] ∗ b [ j ] dp[i][j]=min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+a[i]*b[j] dp[i][j]=min(dp[i1][j1],dp[i][j1],dp[i1][j])+a[i]b[j]。根据乘法的分配律,可以保证这样算的正确性。

这样就可以用一个状态数是 O ( n ∗ m ) O(n*m) O(nm),转移复杂度是 O ( 1 ) O(1) O(1)的dp来解决这个题了。

代码:

#include 
using namespace std;

int n,m,a[2010],b[2010],dp[2010][2010];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		--a[i];
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%d",&b[i]);
		--b[i];
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		dp[i][j]=min(dp[i-1][j-1]+a[i]*b[j],min(dp[i-1][j]+a[i]*b[j],dp[i][j-1]+a[i]*b[j]));
	}
	printf("%d\n",dp[n][m]);
	return 0;
}

你可能感兴趣的:(dp)