(Luogu) P1430 序列取数 (区间dp)

博客围绕整数序列取数问题展开,给定长为n(n<=1000)的整数序列,A和B轮流取数,求A的最终得分。定义F[l][r]表示区间a - b的最大值,给出三种取数选择及对应方程,因原复杂度O(n^3)过高,通过设辅助数组优化递推式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

题意:给定一个长为n的整数序列(n<=1000),由A和B轮流取数(A先取)。每个人可从序列的左端或右端取若干个数(至少一个),但不能两端都取。所有数都被取走后,两人分别统计所取数的和作为各自的得分。假设A和B都足够聪明,都使自己得分尽量高,求A的最终得分。

思路:看视频讲的这道题,喵啊,按照他的思路写了出来。取数肯定是取一段连续的序列,剩下一段连续的序列。

首先定义F[l][r] : [l,r]区间 a-b的最大值(a代表a取得最大和,b代表b取得最大和)。为何这样定义,这样我们会得到两个方程        a-b=F[1][n] 和 a+b=allsum 这样我们就可以得出 a=(F[1][n]+allsum)/2;

面对[l,r]区间我们有3种选择。sum[]为前缀和数组

  • 全取了,F[l][r] = sum[r] - sum[l-1].
  • 从左边取一段 F[l][r] = sum[l'-1] -sum[l] - F[l'][r]  (解释一下为什么是减法呢,因为剩下来的区间 [l'][r] b也会选择最优策略,使得b-a最大,所以a就只能得到 -F[l'][r] 即 b选择下的 a-b的贡献了)
  • 从右边取一段 F[l][r] =sum[r] - sum[r'] - F[l][r']

更新就是在上面三种情况取最大即可,我们可以分析出,枚举长度len,枚举起点l,枚举断点k 是O(n^3)的复杂度,显然过不了。

这就需要耍点手段了。

我们看第三种情况下的递推式 转化 F[l][r] =sum[r] - (sum[r'] + F[l][r'])

我们设P[l][r] = sum[r] + F[l][r], 再设一个Mi[l][r] = min{ P[l][x] |  l<=x<=r },我们想要F[l][r]大,不就是想要P[l][r]最小嘛,所以我们Mi[l][r]的转移也很简单,Mi[l][r]=min(Mi[l][r-1],sum[r]+F[l][r]); (这里P数组没有实际作用,为了简单说明) 第三种情况的转移其实就  F[l][r]=max(F[l][r],sum[r]-Mi[l][r-1]);

同理对于第二个递推式 转化 ( sum[l'-1] - F[l'][r] )-sum[l-1]

这里我们设T[l][r]=sum[l-1] - F[l][r] ) , 用Mx[l][r] = max{ T[x][r] | l<=x<=r }维护,最后我们也可以推得    F[l][r]=max(F[l][r],Mx[l+1][r]-sum[l-1]);

最后代码:

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
#define sc(n) scanf("%d",&n)
#define SC(n,m) scanf("%d %d",&n,&m)
#define SZ(a) int((a).size())
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define drep(i,a,b)	for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-9;
const int maxn=1e3+5;
int a[maxn],sum[maxn],F[maxn][maxn];
int Mx[maxn][maxn],Mi[maxn][maxn],T_T,n;
il void init(int n) {
	for(int i=1; i<=n+2; ++i) {
		sum[i]=0;
		for(int j=1; j<=n+2; ++j) {
			Mx[i][j]=-inf,Mi[i][j]=inf;
		}
	}
}
int main() {
	sc(T_T);
	while(T_T--) {
		sc(n);
		init(n);
		rep(i,1,n)	sc(a[i]),sum[i]+=sum[i-1]+a[i];
		for(int len=1; len<=n; ++len) {
			for(int l=1; l+len-1<=n; ++l) {
				int r=l+len-1;
				F[l][r]=sum[r]-sum[l-1];
				F[l][r]=max(F[l][r],sum[r]-Mi[l][r-1]);
				F[l][r]=max(F[l][r],Mx[l+1][r]-sum[l-1]);
				Mi[l][r]=min(Mi[l][r-1],sum[r]+F[l][r]);
				Mx[l][r]=max(Mx[l+1][r],sum[l-1]-F[l][r]);
			}
		}
		printf("%d\n",(F[1][n]+sum[n])/2);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值