【LsWn的动态规划】区间DP

区间 DP

一般基础状态:\(f(l,r)\) 表示区间为 \([l,r]\) 的答案,然后进行转移。

由于我比较懒,在不需要太多优化时喜欢写递归的区间 \(dp\)

int dp(int l,int r){
	if(f[l][r]) return f[l][r];
	if(l==r) return f[l][r]==...;
	for(int k=...;k<=...;k++) f[l][r]=...;
	return f[l][r];
}

或者

int dp(int l,int r){
	if(f[l][r]) return f[l][r];
	if(l==r) return f[l][r]==...;
	f[l][r]=...(f[l+1][r],f[l][r-1],...);
	return f[l][r];
}

能量项链

自然状态 \(f(l,r)\),破环成链。

\[ f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+a_la_ra_k} \]

#include<bits/stdc++.h>
using namespace std;
const int N=109;
int n,a[N*3],f[N*2][N*2],ans;
int dfs(int u,int v){
	if(f[u][v]) return f[u][v];
	if(u==v) return f[u][v]=0;
	for(int k=u;k<v;k++)f[u][v]=max(f[u][v],dfs(u,k)+dfs(k+1,v)+a[u]*a[v+1]*a[k+1]);
	return f[u][v];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+2*n]=(a[i+n]=a[i]);
	for(int i=1;i<=n;i++) ans=max(ans,dfs(i,i+n-1));
	printf("%d",ans);
	return 0;
}

石子合并

自然状态,破环成链。 然后这题要算两次。

\(f\) 表示最大值。

\[f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+\sum\limits_{i=l}^{r}a_i} \]

#include<bits/stdc++.h>
using namespace std;
const int N=209;
int n,a[N],s[N],f[N][N],g[N][N],ans1,ans2=0x3f3f3f3f;
int dfs1(int l,int r){
	if(f[l][r]) return f[l][r];
	if(l==r) return f[l][r]=0;
	for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dfs1(l,k)+dfs1(k+1,r)+s[r]-s[l-1]); 
	return f[l][r];
}
int dfs2(int l,int r){
	if(g[l][r]!=0x3f3f3f3f) return g[l][r];
	if(l==r) return g[l][r]=0;
	for(int k=l;k<r;k++) g[l][r]=min(g[l][r],dfs2(l,k)+dfs2(k+1,r)+s[r]-s[l-1]); 
	return g[l][r];
}
int main(){
	scanf("%d",&n); memset(g,0x3f,sizeof(g));
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i];
	for(int i=1;i<=2*n;i++) s[i]=s[i-1]+a[i];
	for(int i=1;i<=n;i++) ans1=max(ans1,dfs1(i,i+n-1)),ans2=min(ans2,dfs2(i,i+n-1));
	printf("%d\n%d",ans2,ans1);
	return 0;
}

USACO16 248G

自然状态,要判断是否相等。

扫描二维码关注公众号,回复: 11158283 查看本文章

\[f(l,r)=\max\limits_{l\le k<r,f(l,k)=f(k+1,r)}{f(l,k)+1} \]

#include<bits/stdc++.h>
#pragma optimize("Ofast,unroll-loop")
using namespace std;
const int N=259,ninf=-1e9;
int n,f[N][N],a[N],ans;
int dfs(int u,int v){
	if(f[u][v]) return f[u][v]; f[u][v]=ninf;
	if(u==v) return f[u][v]=a[u];
	for(int k=u;k<=v-1;k++)
		if(dfs(u,k)==dfs(k+1,v)) f[u][v]=max(f[u][v],dfs(u,k)+1);
	return ans=max(ans,f[u][v]),f[u][v];
}
int main(){
	scanf("%d",&n);
	for(register int i=1;i<=n;++i) scanf("%d",&a[i]);
	dfs(1,n),printf("%d",ans);
	return 0;
}

USACO16 262144P

上题加强版,要做一个优化。

如果枚举 \(l,r\) 就需要枚举中间点 \(k\),是件非常耗时间的事情。考虑到其实合并的答案很小,我们答案状态转换,即 \(f(l,p)\) 表示 左端点为 \(l\),答案为 \(p\) 的右端点(可证明右端点唯一)

\[f(l,p)=f(f(l,p-1),p-1) \]

按照倍增的循环方式,先循环 \(p\),再做 \(l\)

#include<bits/stdc++.h>
using namespace std;
int n,a[262525],f[262525][65],ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][a[i]]=i+1;
	for(int p=1;p<=59;p++){
		for(int l=1;l<=n;l++){
			if(!f[l][p]) f[l][p]=f[f[l][p-1]][p-1];
			if(f[l][p]) ans=max(ans,p);
		}
	}
	printf("%d",ans);
	return 0;
}

[USACO06FEB]Treats for the Cows G/S

普通状态,第二种转移。一个水题。

#include<bits/stdc++.h>
using namespace std;
const int N=2009;
int n,a[N],s[N],f[N][N];
int dp(int l,int r){
	if(f[l][r]) return f[l][r];
	if(l==r) return f[l][r]=a[l];
	f[l][r]=max(dp(l,r-1),dp(l+1,r))+s[r]-s[l-1];
	return f[l][r];
}
int main(){
	scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
	printf("%d",dp(1,n));
	return 0;
}

[CQOI2007] 染色

朴素状态。

转移采取一个稍微贪心的思想:如果 \(l,r\) 颜色一样,那么可以先不管其中一个端点,然后去做,即 \(f(l,r)=\min(f(l+1,r),f(l,r-1))\),因为我们本来就是一开始把这个区间全部染成这个相同的颜色,能染一次就满足首尾的颜色。

\[f(l,r)=\min\limits_{l\le k<r} f(l,k)+f(k+1,r) \]

如果首尾相同:

\[f(l,r)=\min(f(l+1,r),f(l,r-1)) \]

#include<bits/stdc++.h>
using namespace std;
const int N=109,inf=0x3f3f3f3f;
int n,f[N][N]; char a[N];
int dp(int l,int r){
	if(f[l][r]!=inf) return f[l][r];
	if(l==r) return f[l][r]=1;
	if(a[l]==a[r]) return f[l][r]=min(dp(l+1,r),dp(l,r-1));
	for(int k=l;k<r;k++) f[l][r]=min(f[l][r],dp(l,k)+dp(k+1,r));
	return f[l][r];
}
int main(){
	memset(f,0x3f,sizeof(f));
	scanf("%s",a+1); n=strlen(a+1);
	printf("%d",dp(1,n));
	return 0;
} 

[IOI1998] Polygon

朴素状态。

对于加法,肯定是选择两个大的相加。

对于乘法,可能遇到两个负数相乘边为正数然后碾压正数的情况。对此,我们记录最小值,每次拿 \(4\) 个状态
(即

\[f(l,k)g(r+1,k),f(l,k)g(k+1,r),g(l,k)g(k+1,r),g(l,k)f(k+1,r) \]


去比较。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=509,inf=1e18;
char e[N]; int n,a[N],f[N][N],g[N][N],mx,ans[N];
int dp(int l,int r){
	if(f[l][r]!=-inf) return f[l][r];
	if(l==r) return f[l][r]=g[l][r]=a[l];
	for(int k=l;k<r;k++){
		dp(l,k),dp(k+1,r);
		if(e[k+1]=='t')
			f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]),g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);
		else
			f[l][r]=max(f[l][r],max(f[l][k]*f[k+1][r],
				max(g[l][k]*g[k+1][r],max(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r])))),
				
			g[l][r]=min(g[l][r],min(g[l][k]*g[k+1][r],
				min(f[l][k]*f[k+1][r],min(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r]))));
	}
	return f[l][r];
}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=2*n;i++) for(int j=1;j<=2*n;j++) f[i][j]=-inf,g[i][j]=inf;
	for(int i=1;i<=n;i++) cin>>e[i]>>a[i],e[i+n]=e[i],a[i+n]=a[i];
	for(int i=1;i<=n;i++){
		ans[i]=dp(i,n+i-1); mx=max(mx,ans[i]);
	}
	printf("%lld\n",mx);
	for(int i=1;i<=n;i++) if(ans[i]==mx) printf("%lld ",i);
	return 0;
}

[HNOI2010] 合唱队

如果用自然状态,我们会发现无法确定现在应该选左边还是右边(因为受到上一个选的影响)。

于是我们定义 \(f(l,r,0/1)\)\([l,r]\) 中现在取左/右。

对于 \(l=r\) 的情况,由于只能算一次,于是我们规定 \(l=r\) 的话只能选右边(只能选左边也行)。

#include<bits/stdc++.h>
using namespace std;
const int N=1009,mod=19650827;
int n,a[N],f[N][N][2],v[N][N][2],tick;
int dp(int l,int r,int status){
	if(v[l][r][status]) return f[l][r][status]; v[l][r][status]=1;
	if(l==r) return f[l][r][status]=!status;
	if(status==0){
		if(a[l]<a[l+1]) f[l][r][0]+=dp(l+1,r,0);
		if(a[l]<a[r]) f[l][r][0]+=dp(l+1,r,1);
	}else{
		if(a[r]>a[r-1]) f[l][r][1]+=dp(l,r-1,1);
		if(a[r]>a[l]) f[l][r][1]+=dp(l,r-1,0);
	} return f[l][r][status]%=mod;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	printf("%d\n",(dp(1,n,0)+dp(1,n,1))%mod);
	return 0;
}

[USACO19DEC]Greedy Pie Eaters P

自然状态

\[f(l,r)=\max(\max f(l,k)+f(k+1,r),\max f(l,k-1)+f(k=1,r)+p(k,l,r)) \]

其中 \(p\) 表示吃的在 \(l,r\) 区间内包括 \(k\) 点的最大体重的牛的体重。

#include<bits/stdc++.h>
using namespace std;
const int N=309;
int n,m,w,l,r,p[N][N][N],f[N][N];
bool vst[N][N][N],v[N][N];
int dpp(int k,int l,int r){
	if(vst[k][l][r]) return p[k][l][r]; vst[k][l][r]=1;
	if(l==r) return p[k][l][r];
	if(r<k||l>k) return 0;
	
	if(r>1) p[k][l][r]=max(p[k][l][r],dpp(k,l,r-1));
	if(l<n) p[k][l][r]=max(p[k][l][r],dpp(k,l+1,r));
	return p[k][l][r];
} 
int dp(int l,int r){
	if(v[l][r]) return f[l][r]; v[l][r]=1;
	if(l>r) return 0;
	for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dp(l,k)+dp(k+1,r));
	for(int k=l;k<=r;k++){
		f[l][r]=max(f[l][r],dp(l,k-1)+dp(k+1,r)+p[k][l][r]);
	}
	return f[l][r];
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&w,&l,&r);
		for(int j=l;j<=r;j++) p[j][l][r]=w;
	}
	for(int k=1;k<=n;k++) dpp(k,1,n);
	printf("%d",dp(1,n));
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/TetrisCandy/p/12810607.html
今日推荐