2021-04-25

ACM区间DP总结
经过老师耐心讲解和自己的总结,使我明白区间DP这么高级的东西,还是挺容易的。也就是在一段区间内的动态规划。
首先确定状态

初始化长度为1(or 2,3…具体因题而异)的dp数组的值

然后枚举区间长度,枚举区间的起始点,(有的题目还需要枚举断点) 由小区间转移到大区间。

最后dp[1][n]往往就是答案。

例题:石子归并,有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,并将新的一堆石子数记为该次合并的得分。主要思想就是一个一个累加: 先看下去是我想知道dp[i][j]的最大值,i表示起始位置,j表示终止位置,所以我肯定是想求出dp[1][4]间的最大值,dp[1][1]=4;dp[2][2]=4;dp[3][3]=5;dp[4][4]=9。然后我在长度为2的时候记录,dp[1][2]=4+4=8,dp[2][3]=8+5=14;dp[3][4]=14+9=23;这样加起来的值就是8+14+23=45;但是如果我反过来呢?dp[1][2]=5+9=14;dp[2][3]=14+4=18;dp[3][4]=18+4=22;这样加起来的值就是14+18+22=54。很明显,54就是所求的最大值。具体来说我们应该定义一个数组dp[i,j]用来表示合并方法,i表示从编号为i的石头开始合并,j表示所求区间的结尾,sum表示的是石头的数量。动态规划的思想:先两两合并,在三三合并,最后再NN合并,在合并过程中,较大的区间可以拆分成已经的小区间进行计算,省时又省力。比如,我可以在三三合并的时候可以拆分成一一还有二三相加。即把当前阶段的合并方法细分成前一阶段已计算出的方法,选择其中的最优方案。
其实吧说实话,难点在于怎么找状态转移方程。(这个还是要看具体题目)。
上个例题演示一下吧。
石子合并。
#include

using namespace std;
int n;
int dp[111][111];//d[i][j]表示从第i堆到第j堆得到最小或最大的分数
int p[111][111];//p[i][j]表示从第i堆到第j堆共有多少【石子】
int a[111];//a[i][j]表示第i堆有多少石子
void Init(int *a)
{
for(int i=1;i<=n;i++)
{
p[i][i]=a[i];//从第i堆合并到第i堆共有a[i]个石子
for(int j=i+1;j<=n;j++)//分别求得第i堆到第i+1堆第i+2堆……石子数量//
{
p[i][j]=p[i][j-1]+a[j];
}
}
}
int getMax()
{
for(int r=2;r<=n;r++)//r控制合并长度,r=2,i<=n-(r-1),j=i+(r-1),最大达到n则dp[i][j]最大表示到d[i][n]//
{
for(int i=1;i<=n-r+1;i++)
{
int j=i+r-1;//j-i=r-1,r每次增加1,比如r=2,d[i][j]可取d[1][2],d[2][3],r增大1d[i][j]可取d[1][3]d[2][4]//
dp[i][j]=dp[i+1][j]+p[i+1][j]+p[i][i];
for(int k=i+1;k<j;k++)
{
dp[i][j]=max(dp[i][j],dp[i][k]+p[i][k]+dp[k+1][j]+p[k+1][j]);//以k为分界,先求k前面的再求k后面的分数,比如求dp[1][4]就分别求(1堆和2到4堆)和(1到2,2到4)和(1到3堆,第4堆)。再以(1到2,2到4)为例d[1][4]=d[1][2]+d[3][4]+p[1][2]+p[3][4],先是(1,2)(3,4)分别合并得分,第二部合起来的两堆再合并得分为这两堆的石子总数之和//
}
}
}
return dp[1][n];
}
int getMin()
{
for(int r=2;r<=n;r++)
{
for(int i=1;i<=n-r+1;i++)
{
int j=i+r-1;
dp[i][j]=dp[i+1][j]+p[i+1][j]+p[i][i];
for(int k=i+1;k<j;k++)
{
dp[i][j]=min(dp[i][j],dp[k+1][j]+dp[i][k]+p[k+1][j]+p[i][k]);
}关键步骤在此 ,即转移方程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用python中的pymsql完成如下:表结构与数据创建 1. 建立 `users` 表和 `orders` 表。 `users` 表有用户ID、用户名、年龄字段,(id,name,age) `orders` 表有订单ID、订单日期、订单金额,用户id字段。(id,order_date,amount,user_id) 2 两表的id作为主键,`orders` 表用户id为users的外键 3 插入数据 `users` (1, '张三', 18), (2, '李四', 20), (3, '王五', 22), (4, '赵六', 25), (5, '钱七', 28); `orders` (1, '2021-09-01', 500, 1), (2, '2021-09-02', 1000, 2), (3, '2021-09-03', 600, 3), (4, '2021-09-04', 800, 4), (5, '2021-09-05', 1500, 5), (6, '2021-09-06', 1200, 3), (7, '2021-09-07', 2000, 1), (8, '2021-09-08', 300, 2), (9, '2021-09-09', 700, 5), (10, '2021-09-10', 900, 4); 查询语句 1. 查询订单总金额 2. 查询所有用户的平均年龄,并将结果四舍五入保留两位小数。 3. 查询订单总数最多的用户的姓名和订单总数。 4. 查询所有不重复的年龄。 5. 查询订单日期在2021年9月1日至9月4日之间的订单总金额。 6. 查询年龄不大于25岁的用户的订单数量,并按照降序排序。 7. 查询订单总金额排名前3的用户的姓名和订单总金额。 8. 查询订单总金额最大的用户的姓名和订单总金额。 9. 查询订单总金额最小的用户的姓名和订单总金额。 10. 查询所有名字中含有“李”的用户,按照名字升序排序。 11. 查询所有年龄大于20岁的用户,按照年龄降序排序,并只显示前5条记录。 12. 查询每个用户的订单数量和订单总金额,并按照总金额降序排序。
最新发布
06-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值