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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章