区间dp小练

本文精选了多个区间动态规划的经典案例,包括石子合并、圆形石子合并、能量项链等,详细介绍了每道题目的解题思路及实现代码,帮助读者深入理解区间DP的核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提纲:

区间dp一般设计f[i][j]表示区间i到j的dp值,用几段小的合并成一段整体,也是分治的思想,转移时枚举中间点k,从f[i][k],f[k+1][j]来合并

1.题目:

题解:石子归并

水题开头

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,a[105],sum[105],f[105][105];
int main()
{
	int i,j,k;
	scanf("%d",&n);
	for (i=1;i<=n;i++) 
	  scanf("%d",&a[i]),sum[i]=a[i]+sum[i-1];
	//f[i][j]从i合并到j的最小值
	memset(f,0x7f,sizeof(f));
	for (i=1;i<=n;i++)
	  f[i][i]=0;
	for (i=1;i<=n;i++)//区间大小 
	  for (j=1;j<=n-i+1;j++)//起点 
	    for (k=j;k<j+i-1;k++) 
		  f[j][j+i-1]=min(f[j][j+i-1],f[j][k]+f[k+1][j+i-1]+sum[j+i-1]-sum[j-1]);
	printf("%d",f[1][n]);
}

2.题目: 圆形石子归并

题解:

水题的扩展版本

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;
int n,a[105];
LL f[105][105],sum[105];
int main()
{
	int i,j,k;
	scanf("%d",&n);
	for (i=1;i<=n;i++)
	  scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	for (i=n+1;i<=n*2;i++) a[i]=a[i-n],sum[i]=sum[i-1]+a[i];
	
	memset(f,0x7f,sizeof(f));
	LL ans1=f[0][0];//随意一个最大值 
	for (i=1;i<=2*n;i++) f[i][i]=0;
    for (i=1;i<=n;i++)
      for (j=1;j<=2*n-i;j++)
        for (k=j;k<j+i-1;k++)
          f[j][j+i-1]=min(f[j][j+i-1],f[j][k]+f[k+1][j+i-1]+sum[j+i-1]-sum[j-1]);
	for (i=1;i<=n;i++)
      ans1=min(ans1,f[i][i+n-1]);
      
    LL ans2=0;
    memset(f,0,sizeof(f));
    for (i=1;i<=n;i++)
      for (j=1;j<=2*n-i;j++)
        for (k=j;k<j+i-1;k++)
          f[j][j+i-1]=max(f[j][j+i-1],f[j][k]+f[k+1][j+i-1]+sum[j+i-1]-sum[j-1]);
    for (i=1;i<=n;i++)
      ans2=max(ans2,f[i][i+n-1]);  
      
    printf("%lld\n%lld",ans1,ans2);
}

3.题目:能量项链

题解:也是一样的,按照他说的合并加分就好了

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int a[105],f[205][405];
int main()
{
	int n,i,j,k;
	scanf("%d",&n);
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	for (i=n+1;i<=2*n;i++) a[i]=a[i-n];
	for (i=1;i<=n;i++)
	  for (j=1;j<=2*n-i;j++)
	    for (k=j;k<j+i-1;k++)
	      f[j][j+i-1]=max(f[j][j+i-1],f[j][k]+f[k+1][j+i-1]+a[j]*a[j+i]*a[k+1]);
	int ans=0;
	for (i=1;i<=n;i++)
	  ans=max(ans,f[i][i+n-1]);
	printf("%d",ans);
}

4.题目:玩具起名

题解:f[i][j][0/1/2/3]表示从i字母到j字母能否转化为W/I/N/G 

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
//f[i][j]['W'/'I'/'N'/'G']表示从i字母到j字母能否转化为W/I/N/G 
using namespace std;
struct hh{int x,y,z;}zh[80];
bool f[205][205][100];
char st[250];
int main()
{
	int W,I,N,G,i,cnt=0,j,k,tt;
	scanf("%d%d%d%d\n",&W,&I,&N,&G);
	
	for (i=1;i<=W;i++) scanf("%s",st),zh[++cnt].x=st[0],zh[cnt].y=st[1],zh[cnt].z='W';
	for (i=1;i<=I;i++) scanf("%s",st),zh[++cnt].x=st[0],zh[cnt].y=st[1],zh[cnt].z='I';
	for (i=1;i<=N;i++) scanf("%s",st),zh[++cnt].x=st[0],zh[cnt].y=st[1],zh[cnt].z='N';
	for (i=1;i<=G;i++) scanf("%s",st),zh[++cnt].x=st[0],zh[cnt].y=st[1],zh[cnt].z='G';
	
	scanf("%s",st+1);int len=strlen(st+1); 
	for (i=1;i<=len;i++) f[i][i][st[i]]=true;
	
	for (i=2;i<=len;i++)
	  for (j=1;j<=len-i+1;j++)
	    for (k=j;k<=j+i-1;k++)
	      for (tt=1;tt<=cnt;tt++)
	        f[j][i+j-1][zh[tt].z]|=f[j][k][zh[tt].x] && f[k+1][i+j-1][zh[tt].y];
	      
	bool fff=false;
	if (f[1][len]['W']) fff=true,printf("W");
	if (f[1][len]['I']) fff=true,printf("I");
	if (f[1][len]['N']) fff=true,printf("N");
	if (f[1][len]['G']) fff=true,printf("G");
	if (!fff) printf("The name is wrong!");
}

题目:删数

题解:这题目一读就觉得是区间dp嘛

代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
int a[105],f[105][105];
int cost(int i,int j){if (i==j) return a[i];else return abs(a[j]-a[i])*(j-i+1);}
int main()
{
	int n,i,j,k;
	scanf("%d",&n);
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	for (i=1;i<=n;i++) f[i][i]=a[i];
	//f[i][j]从i合并到j的最大值 
	for (i=2;i<=n;i++)
	  for (j=1;j<=n-i+1;j++)
	  {
	  	int r=j+i-1;
	  	f[j][r]=cost(j,r);
		for (k=j;k<r;k++)
	  	  f[j][r]=max(f[j][r],f[j][k]+f[k+1][r]);
	  }
    printf("%d",f[1][n]);
}



### 三、动态规划(DP)的学习路径与掌握方法 动态规划是一种将复杂问题分解为子问题,并通过存储子问题的解来避免重复计算的算法策略。其核心思想是“记忆化”与“状态转移”,广泛应用于计算机科学、数学优化和算法竞赛中[^1]。掌握动态规划需要系统地学习状态定义、转移方程、边界条件以及常见优化技巧。 #### 状态定义与建模 动态规划的第一步是正确地定义状态。状态应能完整描述问题的某个阶段,并具备最优子结构。例如,在背包问题中,状态通常表示为 `dp[i][j]`,表示前 `i` 个物品中选择,总容量为 `j` 的最大价值。在序列问题中,状态可以表示为 `dp[i]`,表示以第 `i` 个元素结尾的最优解。 ```cpp // 01 背包问题状态定义示例 int dp[1005][1005]; // dp[i][j] 表示前i个物品在容量j下的最大价值 ``` #### 状态转移方程 状态转移方程是动态规划的核心部分,它描述了如何从已知状态推导出新状态。例如,01 背包问题的状态转移方程为: ``` dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) ``` 其中 `w[i]` 是第 `i` 个物品的重量,`v[i]` 是其价值。这表示在考虑第 `i` 个物品时,可以选择放入或不放入。 #### 边界条件与初始化 动态规划问题通常需要设置初始状态。例如,斐波那契数列的初始条件为 `dp[0] = 0`,`dp[1] = 1`。对于二维 DP 问题,如最长公共子序列(LCS),初始化时通常设置 `dp[0][j] = 0` 和 `dp[i][0] = 0`。 ```cpp // LCS 初始化 for (int i = 0; i <= n; i++) dp[i][0] = 0; for (int j = 0; j <= m; j++) dp[0][j] = 0; ``` #### 常见 DP 类型与应用场景 - **背包问题**:包括 01 背包、完全背包、多重背包等。 - **区间 DP**:适用于合并类问题,如石子合并、括号匹配等。 - **树形 DP**:在树结构上进行状态转移,常用于树的直径、最大独立集等问题。 - **数位 DP**:用于处理数字每一位的状态,常用于统计满足某种性质的数字个数。 - **插头 DP(轮廓线 DP)**:一种高级动态规划技术,适用于连通性问题,如网格路径、插头连接等,虽然编码难度较大,但能解决传统方法无法处理的问题[^2]。 #### 优化技巧 - **滚动数组**:当状态转移只依赖前一阶段时,可以使用滚动数组压缩空间。 - **单调队列优化**:适用于滑动窗口中的最大值或最小值问题。 - **斜率优化**:适用于某些具有特定形式的状态转移方程,可将复杂度从 $O(n^2)$ 优化到 $O(n)$。 - **矩阵快速幂**:适用于状态转移具有线性递推结构的问题,如斐波那契数列。 ```cpp // 滚动数组优化 01 背包 int dp[1005]; for (int i = 1; i <= n; i++) { for (int j = W; j >= w[i]; j--) { dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } ``` #### 实战训与真题演 建议从经典 DP 问题入手,如: - 背包问题(01 背包、完全背包) - 最长公共子序列(LCS) - 最长递增子序列(LIS) - 石子合并问题 - 数字三角形问题 随后逐步挑战更复杂的题目,如历年 CSP-S 复赛中的 DP 题目,例如 2021 年“括号序列”问题,它结合了动态规划与贪心思想,对状态转移的建模要求较高。 #### 时间管理与学习计划 - **第一阶段(基础巩固)**:掌握基本 DP 模型和状态转移方式。 - **第二阶段(专题训)**:深入学习各类 DP 专题,如区间 DP、树形 DP、数位 DP 等。 - **第三阶段(真题实战)**:通过历年 CSP、NOI、ACM-ICPC 等比赛真题进行训,提升综合解题能力。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值