P1430 序列取数(区间DP)

P1430 序列取数(区间DP)

传送门

思路:区间dpdp想了好半天+看题解才搞明白,这里再梳理一遍。

显然当AA的取值和最大时,因为sumA+sumB=sum_A+sum_B=常数,所以sumAsumBsum_A-sum_B最大。

dp[l][r]dp[l][r]为区间[l,r],AB[l,r],A和B的最大差值。

因为dp[1][n]=sumAsumBdp[1][n]=sum_A-sum_B

所以sumA=(dp[1][n]+pre[n])2sum_A=\dfrac{(dp[1][n]+pre[n])}{2}

当先手取左区间[l,l1][l,l'-1],剩余区间为[l,r][l',r],l[l+1,r]l'\in[l+1,r]时有转移方程:

dp[l][r]=max(dp[l][r],pre[l1]pre[l1]dp[l][r])dp[l][r]=max(dp[l][r],pre[l'-1]-pre[l-1]-dp[l'][r])

表示的是先手只能取得左区间的数[l,l1][l,l'-1]和后面的状态(之前的后手变为先手的最大差值dp[l][r]dp[l'][r])。

右区间同理,剩余区间为[l,r][l,r']

dp[l][r]=amx(dp[l][r],pre[r]pre[r]dp[l][r])dp[l][r]=amx(dp[l][r],pre[r]-pre[r']-dp[l][r']).

因为这样是O(n3)O(n^3)的,所以考虑优化。

显然我们可以预处理左区间计算时的pre[l1]dp[l][r]pre[l'-1]-dp[l'][r]的最大值和右区间的pre[r]+dp[l][r]pre[r']+dp[l][r']的最小值。

mx[l][r]=max(pre[k1]dp[k][r]),k[l+1,r].mx[l][r]=max(pre[k-1]-dp[k][r]),k\in[l+1,r].

mn[l][r]=min(pre[k]+dp[l][k]),k[l,r1].mn[l][r]=min(pre[k]+dp[l][k]),k\in[l,r-1].

然后对这两个进行区间更新维护即可。

时间复杂度:O(n2)O(n^2)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first 
#define se second
int mx[N][N],mn[N][N],f[N][N],pre[N],a[N];
//mx[i][j]  pre[k-1]-f[k][j] k 属于[i+1,j] 
//mn[i][j] pre[k]+f[i][k] k属于[i,j-1] 
int main(){
	int t; 
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);
		for(int i=0;i<=n+1;i++){
			pre[i]=0;
			for(int j=1;j<=n+1;j++) f[i][j]=mx[i][j]=mn[i][j]=0;
		}
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),pre[i]=pre[i-1]+a[i];
		for(int i=1;i<=n;i++){
			f[i][i]=a[i],mx[i][i]=pre[i-1]-f[i][i];
			mn[i][i]=pre[i]+f[i][i]; 
		}
		for(int k=2;k<=n;k++)
			for(int l=1,r=k;r<=n;l++,r++){
				f[l][r]=pre[r]-pre[l-1];
				f[l][r]=max(f[l][r],mx[l+1][r]-pre[l-1]);
				f[l][r]=max(f[l][r],pre[r]-mn[l][r-1]);
				mx[l][r]=max(mx[l+1][r],pre[l-1]-f[l][r]);
				mn[l][r]=min(mn[l][r-1],pre[r]+f[l][r]); 
			}
			printf("%d\n",(f[1][n]+pre[n])/2); 
	}
	return 0;
}

思路2:参考另一位大佬的,我觉得比上面一种方法好,但是容易超时,自己手写minmin能快一倍的速度。。。

传送门

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first 
#define se second
int a[N],dp[N][N],f[N][N],g[N][N];
inline void read(int &x){ 
	x=0;int w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
	for(;ch>='0'&&ch<='9';ch=getchar())
		x=(x<<3)+(x<<1)+(ch&15);
	x*=w; 
}
int main(){
	int t; 
	read(t);
	while(t--){
		int n;
		read(n);
		for(reg int i=1;i<=n;i++){
			read(a[i]),dp[i][i]=f[i][i]=g[i][i]=a[i];
			a[i]+=a[i-1];
		}
		for(reg int k=2;k<=n;k++)
			for(reg int l=1,r=k;r<=n;l++,r++){
				 int tmp=0;
				 if(f[l+1][r]<g[l][r-1]) tmp=f[l+1][r];
				 else tmp=g[l][r-1];
				 if(tmp>0) tmp=0;
				 dp[l][r]=a[r]-a[l-1]-tmp;
				 if(f[l+1][r]>dp[l][r]) f[l][r]=dp[l][r];
				 else f[l][r]=f[l+1][r];
				 if(g[l][r-1]>dp[l][r]) g[l][r]=dp[l][r];
				 else g[l][r]=g[l][r-1];
			}
			printf("%d\n",dp[1][n]); 
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章