洛谷—P1043 数字游戏(区间DP)

在这里插入图片描述
解题思路:
这道题我写的状态转移方程是:
dp[i][j][k]从i到j的m个合积最优结果
dp[i][j][1]=arr[i]+arr[i+1]+…+arr[j]
dp[i][j][m]=max(dp[i][j][m],dp[i][k][m-l]*dp[k+1][j][l],dp[i][k][l]*dp[k+1][j][m-l])
枚举l从1~m/2
容易理解,但是做法不够简练

#include<bits/stdc++.h>
using namespace std;
int dp1[110][110][10],dp2[110][110][10],n,m,arr[110],presum[110],ans1,ans2=10;
int f(int i,int j,int m)
{
	if(i==j) return arr[i];
	if(dp1[i][j][m]>0) return dp1[i][j][m];
	int ans=0;
	for(int l=1;l<=m/2;++l)
	{
		for(int k=i;k<j;++k)
		ans=max(ans,max(f(i,k,l)*f(k+1,j,m-l),f(i,k,m-l)*f(k+1,j,l)));
	}
	dp1[i][j][m]=ans;
	return ans;
}
int h(int i,int j,int m)
{
	if(i==j) return arr[i];
	if(dp2[i][j][m]!=16843009) return dp2[i][j][m];
	int ans=100;
	for(int l=1;l<=m/2;++l)
	{
		for(int k=i;k<j;++k)
		ans=min(ans,min(h(i,k,l)*h(k+1,j,m-l),h(i,k,m-l)*h(k+1,j,l)));
	}
	dp2[i][j][m]=ans;
	return ans;
}
int main()
{
	memset(dp1,-1,sizeof(dp1));
	memset(dp2,1,sizeof(dp2));
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		int temp;
		cin>>temp;
		arr[i]=(temp+10000)%10;
		arr[i+n]=arr[i];
		presum[i]=presum[i-1]+arr[i];
	}
	for(int i=n+1;i<=(n<<1);++i)
	presum[i]=presum[i-1]+arr[i];
	for(int i=1;i<=2*n;++i)
	{
		for(int j=i;j<=2*n;++j)
		{
			dp1[i][j][1]=dp2[i][j][1]=(presum[j]-presum[i-1])%10;
		}
	}
	for(int i=1;i<=n;++i)
	{
		ans1=max(ans1,f(i,i+n-1,m));
		ans2=min(ans2,h(i,i+n-1,m));
	}
	cout<<ans2<<endl;
	cout<<ans1<<endl;
	return 0;
}

做题过程中出现的几个小问题:

  1. 数组初始化值问题
  2. ans=max(dp1,dp2)可能造成最值丢失
  3. 数的取值范围为问题

更优解:
区间DP简单说就是以某一段区间为状态,就是从小的区间开始算,用已经算好的小区间来推大区间,基本上的模型是f(i,j)(表示i-j区间的状态)=f(i,k)+f(k,j)(i-k区间和k-j区间的状态,k是分段的位置)。

for(re int i=1;i<=n;i++) f[i][0]=d[i][0]=sum[1][i];
//在分段次数为0时,前i个数的最小(最大)值就是它们的和
for(re int j=1;j<=m;j++)
    for(re int i=j+1;i<=n;i++)
        for(re int k=1;k<i;k++)
        {
            f[i][j]=std::min(f[i][j],f[k][j-1]*sum[k+1][i]);
            d[i][j]=std::max(d[i][j],d[k][j-1]*sum[k+1][i]);
        }

完整代码:

#include<bits/stdc++.h>
#define M 150
#define re register
#define li inline
int n,m,a[M],sum[M][M],f[M][M],d[M][M],minn=1e9,maxn;
li int read()
{
    re int f=1,j=0;
    re char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())j=(j<<1)+(j<<3)+c-48;
    return f*j;
}
li void write(re int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
li void _dp(re int p)
{
    for(re int i=1+p;i<=n+p;i++)
        for(re int j=i;j<=n+p;j++)
            sum[i][j]=sum[i][j-1]+a[j];
    for(re int i=1+p;i<=n+p;i++)
        for(re int j=i;j<=n+p;j++)
        {
            sum[i][j]%=10;
            if(sum[i][j]<0) sum[i][j]+=10;
        }
    for(re int i=1+p;i<=n+p;i++)
        for(re int j=1;j<=m;j++)
            f[i][j]=1e8,d[i][j]=0;
    for(re int i=1+p;i<=n+p;i++) f[i][0]=d[i][0]=sum[1+p][i];
    for(re int j=1;j<=m;j++)
        for(re int i=j+1+p;i<=n+p;i++)
            for(re int k=1+p;k<i;k++)
            {
                f[i][j]=std::min(f[i][j],f[k][j-1]*sum[k+1][i]);
                d[i][j]=std::max(d[i][j],d[k][j-1]*sum[k+1][i]);
                //std::cout<<d[i][j]<<" "<<d[k][j-1]<<" "<<sum[k+1][i]<<" "<<i<<" "<<j<<" "<<k<<std::endl;
            }
    maxn=std::max(maxn,d[n+p][m-1]); //由于实质上是分成m快,所以是切m-1刀
    minn=std::min(minn,f[n+p][m-1]);
    return ;
}
signed main()
{
    n=read(),m=read();
    for(re int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
    for(re int i=0;i<n;i++) _dp(i);
    write(minn);
    putchar('\n');
    write(maxn);
    return 0;
}
发布了165 篇原创文章 · 获赞 11 · 访问量 4887

猜你喜欢

转载自blog.csdn.net/weixin_43784305/article/details/104503728