ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

区间DP の 题(内含 最长回文串,石子合并,删除字符串,乘积最大,释放囚犯)

2022-08-18 12:02:01  阅读:140  来源: 互联网

标签:囚犯 乘积 int 最大值 long a1a2 DP dp 回文


乘积最大

  由于题目给定的是m,需要分解成m+1部分的乘积,不难想到乘号刚好是m个,那么该题就转化成了m个乘号的插入方式。 

 最优子结构分析:     

 设数字字符串为a1a2…an        

   m=1 时,一个乘号可以插在a1a2…an中的n-1个位置,这样就得到n-1种乘积:                        

     a1*a2…an,   a1a2*a3…an, …, a1a2…a n-1*an           

    此时的最大值= max { a1*a2…an, a1a2*a3…an, …, a1a2…a n-1*an }        

   m=2时,两个乘号可以插在a1a2…an中n-1个位置的任两个地方,这样总共会产生 

  个乘积。把这些乘积分个类,便于观察规律。 

Case1: a1a2 …*a n-1*an , a1a2 …*a n-2 a n-1*an , a1*a2 …a n-3 a n-2 a n-1 *  an ,   

  因后一个乘号位置不变,要使这些乘积最大,就要找出在前n-1个数中插入一个乘号的最大值。引入符号F(n-1,1)表示在前n-1个数中插入一个乘号的最大值,则  Case1的最大值为F(n-1, 1) * an

Case2: a1a2 …*a n-2*a n-1 an , a1a2 …*a n-3 a n-2* a n-1 an , a1*a2 …a n-3 a n-2 * a n-1 an ,     

  因后一个乘号位置不变,要使这些乘积最大,就要找出在前n-2个数中插入一个乘号的最大值。设符号F(n-2)(1)为在前n-2个数中插入一个乘号的最大值,则 Case2的最大值为F(n-2,1) * a n-1 an 

同理,Case3的最大值为F(n-3,1) * a n-2 a n-1 an  …… Case n-2的最大值为F(2,1) * a3…an 

把一段数字串转化为一个整数:

int get(int i,int j) { //将i~j这一段数字转化为一个整数 
	int ret=0;
	for(int k=i;k<=j;k++) {
		ret=ret*10+s[k]-'0';
	}
	return ret;
}

状态转移方程推导: 

  下面以n=9, m=4, s=‘321044105’为例,说明计算过程。              

  我们引入符号f(i,j)表示从a1~ai中插入j个乘号取得的最大值,g[i][j]表示从ai~aj的子串的数值。则上式可表示为:    

  F(9,4) = max{f(8,3)*g[9][9], f(7,3)*g[8][9], f(6,3)*g[7][9], f(5,3)*g[6][9], f(4,3)*g[5][9]}    

  F(8,3) = max{f(7,2)*g[8][8], f(6,2)*g[7][8], f(5,2)*g[6][8], f(4,2)*g[5][8],f(3,2)*g[4][8]}    

  F(7,3) = max{f(6,2)*g[7][7], f(5,2)*g[6][7], f(4,2)*g[5][7], f(3,2)*g[4][7]}    

   …………    

  上面的分析已经看出问题的最优子结构、重复子问题性质。 

 定义状态:dp[i][j]表示从a1~aj中插入i个乘号取得的最大值

dp[i][j] = max{ dp(i-1, k)*get(k+1, j) }  (1<=i<=m, i+1<=j<=n, i<=k<j )

  i的取值范围为:乘号个数

  j的取值范围为:乘号数i加1 ~ 数字串总个数

  k的取值范围为:乘号个数~右边界j减去1

  上式的边界条件是什么?

  dp[0][i] = get(1, i)  (1<=i<=n)

  我们要求的问题的最优解是dp[m][n] 

 

Code
 #include <bits/stdc++.h>
using namespace std;
long long n,k,dp[45][45],m;
char a[45];
long long num(long long x,long long y)
{
	long long sum=0;
	for(long long i=x;i<=y;i++)
	{
		sum=sum*10+a[i]-'0';
	}
	return sum;
 } 
int main()
{
	scanf("%lld%lld",&m,&n);
	scanf("%s",a+1);
	for(int i=1;i<=m;i++)
	{
		dp[0][i]=num(1,i);
	}
	for(long long i=1;i<=n;i++)
	{
		for(long long j=i+1;j<=m;j++)
		{
			for(long long k=i;k<j;k++)
			{
				dp[i][j]=max(dp[i-1][k]*num(k+1,j),dp[i][j]);
			}
		}
	}
	printf("%lld\n",dp[n][m]);
}

 

删除字符串

  定义dp[i][j]为删除区间[i,j]的最少次数

  (1)如果s[i]==s[j],dp[i][j] = dp[i+1][j-1] + 1,即先删除区间[i+1,j-1]再把相同的s[i]s[j]一次删除;

  (2)如果s[i] != s[j],dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1,只能先删除区间[i+1,j]或者[i,j+1],最后删除区间端点的单个字符;但是如果有aabb这种串的话,上面第二种做法就要删3次,显然不对!

  (3)然后枚举区间[i,j]的分割点k,   dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]-1),这样的话k这个点删了两次,所以要减1。 

 

#include<bits/stdc++.h>
using namespace std;
char c[505];
int main() 
{
	int n, idx, a[505], dp[505][505];
    scanf("%d",&n);
    scanf("%s",c);
    a[0]=c[0];
    for (int i = 0; i < n; i++) 
        if (c[i]!= c[i-1])
            a[++idx] = c[i];
    for(int i=1;i<=idx;i++)
    	dp[i][i]=1;
    for (int len=2;len<=idx; len++) 
	{
        for (int i=1;i<=idx-len+1;i++)
		 {
		 	int j=i+len-1;
            if(a[i]==a[j])
            	dp[i][j]=dp[i+1][j-1]+1;
			else
				dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1;
           	for(int k=i+1;k<=j;k++)
           		dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]-1);
        }
    }
    printf("%d", dp[1][idx]);
    return 0;
}

 

石子合并

   假设只有2堆石子,显然只有1种合并方案  如果有3堆石子,则有2种合并方案,((1,2),3)和(1,(2,3))  如果有k堆石子呢? 

                           

不管怎么合并,总之最后总会归结为2堆,如果我们把最后两堆分开,左边和右边无论怎么合并,都必须满足最优合并方案,整个问题才能得到最优解。

 令m[i,j]表示归并第i个数到第j数的最小代价,w[i,j]表示第i个数到第j个数的和,这个可以事先计算出来。有如下的状态转移方程: 

                 

预处理w[i][j]:  

  先算出前缀和s[i],然后w[i][j] = s[j] – s[i-1]  

因此,w[i][j]可以不事先存放,在DP时直接O

  (1)计算   s[0] = 0;   for(int i = 1; i<=n; i++) s[i] = s[i-1]+a[i]; 

拓展成环:  

  对于环状序列,通用的处理方法是把这条链延长2倍,扩展成2n-1堆,其中第1堆与n+1堆完全相同,第i堆与n+i堆完全相同,这样我们只要对这2n堆动态规划后,枚举f(1,n),f(2,n+1),…,f(n,2n-1)取最优值即可即可。 

                         

 

Code
 #include<bits/stdc++.h>
using namespace std;
int dp[305][305],len,a[305],n,sum[305];
int main()
{
	cin>>n;
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		dp[i][i]=0;
	}
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i<=n-len+1;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++)
			{
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
			}
		}
	}
	cout<<dp[1][n];
}

 

最长回文串

  令dp[i][j]表示在字符串区间s[i]..s[j]之间的最长回文串的长度。  

  分几种情况讨论:

(1)如果s[i] = s[j],这时如果s[i+1]..s[j-1]是回文串,则加上两端的回文,可构成一个更长的回文串。怎以判断s[i+1]..s[j-1]是不是回文串呢?   一个非常简单的办法是:dp[i+1][j-1]是否等于j-i-1 

                         

  如果s[i+1]..s[j-1]不构成回文串,则dp[i][j]在dp[i+1][j]与dp[i][j-1]中取较大值。

(2)如果s[i]!=s[j],则dp[i][j]在dp[i+1][j]与dp[i][j-1]中取较大值者这仍然是一个区间DP的模型。 

Code
 #include<bits/stdc++.h>
using namespace std;
int dp[3005][3005];
int main()
{
	char s[3005];
	scanf("%s",s+1);
	int n=strlen(s+1);
	for(int i=1;i<=n;i++)
	{
		dp[i][i]=1;
	}
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i<=n-len+1;i++)
		{
			int j=i+len-1;
			if(dp[i+1][j-1]==j-i-1&&s[i]==s[j])
			{
				dp[i][j]=dp[i+1][j-1]+2; 
			}
			else
			{
				dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
			}
		}
	}
	printf("%d",dp[1][n]);
	return 0;
}

 

释放囚犯

  区间dp的套路:设f[i][j]为区间释放i~j号囚犯所需最少的肉(注意,i,j不是牢房编号,是释放的囚犯编号,也就是下面的a[i]数组)

  枚举区间的分界点k,转移方程为:

  f[i][j]=min{f[i][j],f[i][k-1]+f[k+1][j]+a[j+1]-a[i-1]-1-1}

  把后面这一坨拿出来拆开看看,

  f[i][k-1]+f[k+1][j],这个不必解释

  a[j+1]-a[i-1]-1就是第j+1个要放出的囚犯到第i-1个要放出的囚犯之间的人数,也就是要发的肉的数量;

  最后一个-1 是什么呢,就是第k个放出去的囚犯,不用给他吃肉了

  注意一件事:输入的囚犯的编号。当你细细的观察它们时,你会发现第 Qi​ 个囚犯的编号Qi​ 等于他及其前面的所有人的人数,那么这就相当于是一个前缀和,又因为我们放第 Qq 个囚犯的时候需要给最后一段人肉,所以我们可以假设在这段监狱的最后(p+1)还有一个需要释放的囚犯。

  再假设我们此时释放囚犯 k,那么我们此时需要的肉的数量即为释放第 Qi​ 个囚犯到第 Qk−1​ 个囚犯与释放第 Qk+1​ 个囚犯到第 Qj​ 个囚犯所需的总肉数加上施放这个囚犯所需的肉的数量。由于我们先选择释放第Qk​ 个囚犯,所以我们需要用 a[j+1]-a[i-1]-2 的肉(我们的假设是除了第 Qi​ 个囚犯到第 Qj​ 个囚犯未释放外其他囚犯均已释放,只不过没用肉。),由于先放哪一个囚犯最优不清楚,于是取最小值。

#include<bits/stdc++.h> 
using namespace std;
int a[105];
int dp[105][105];
int main()
{
    int p,q;
    scanf("%d%d",&p,&q);
    for(int i=1;i<=q;i++)
        scanf("%d",&a[i]);
    a[0]=0;
    a[q+1]=p+1;
    sort(a+1,a+q+1);
    for(int len=1;len<=q;len++)
    {
        for(int i=1;i+len-1<=q;i++)
        {
            int j=i+len-1;
            dp[i][j]=0x3f3f3f3f;
            for(int k=i;k<=j;k++)
                dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[j+1]-a[i-1]-2);
        }
    }
    printf("%d",dp[1][q]);
    return 0;
}

 

标签:囚犯,乘积,int,最大值,long,a1a2,DP,dp,回文
来源: https://www.cnblogs.com/pangtuan666/p/16598200.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有