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

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

时间:2022-08-18 12:04:14浏览次数:68  
标签:囚犯 乘积 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,回文
From: https://www.cnblogs.com/pangtuan666/p/16598200.html

相关文章

  • [题解] HDU 5115 Dire Wolf 区间DP
    考虑先枚举所有的物品中最后拿走的,这样就分成了2个子问题,即先拿完左边的,再拿完右边的,最后拿选出的那个。令dp(i,j)表示拿完[i,j]所有物品的最小代价。你可能会说,我们拿[i,j......
  • 计数类组合数DP(玩个球)A3
    n种颜色的球,每种m个,给出一个这些球的排列,就会从左往右把第一个遇到的某种颜色涂白。问有多少不一样的颜色序列(n,m<=2000)发现白色的球都一样,当成一种算,我们只关注当前:(1)白......
  • Chiitoitsu(概率DP)
    代码#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<map>usingnamespacestd;typedeflonglongll;constintN=1......
  • 状压dp
    朴素状压对于数据范围小的题一定要对状压绝对敏感这里的范围小不一定是\(n\)的范围小,而是任何有关信息小于\(20\)时要引起注意P3052[USACO12MAR]CowsinaSkyscr......
  • 达梦dexpdp,dimpdp导出导入数据
    1.创建directorySQL>createdirectorydirectas'/dm8/direct';executedsuccessfullyusedtime:48.272(ms).Executeidis503.2.创建测试用户SQL>createuse......
  • 「AGC012F」Prefix Median 题解 (DP)
    题目简介给定一个长度为\(2n-1\)的序列\(a\),你可以随意排列\(a\)中的元素,请求出有多少种不同的序列\(b\),满足\(b\)的长度为\(n\)。\(b_i=\{a_1\ldotsa_{2......
  • [2016年NOIP普及组] 回文日期
    [2016年NOIP普及组]回文日期题目大意:用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月 份,最后 2 位代表日期,一个日期是回文的,当且仅当表示这个日......
  • 期望dp
    期望的线性性质:E(ax+by)=aE(X)+bE(Y)1-n总长度的期望到达某个结果的期望值=这个结果*从起始状态到这个状态的概率f[i]=∑1/k*(w[i]+f(S[i])f[i]表示从i走到n的期......
  • Codeforces1699E Three Days Grace【数学】【DP】
    分析:一开始觉得是二分答案,发现行不通之后改为枚举最小值。现在我将这若干个数分解,假设分解完之后得到的最小值为$i$,那么我就是要在最小值为$i$的基础上尽量最小化分解的......
  • 8、ThreadPoolTaskExecutor线程并发
    一、线程池的优点:1、降低资源消耗。通过重复利用自己创建的线程降低线程创建和销毁造成的消耗。2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行......