acm初学的第六周

这周主要做了一些难度不大的动态规划的题
一、整理一下做过的所有子序列题
1.连续子序列问题
(1)最大连续子序列的和,并且输入子序列的首尾两个位置

k=1;
for(i=0;i<n;i++)
{ sum+=a[i];
if(sum>summax)
{ summax=sum; be=k; en=i+1;}
if(sum<0)
{sum=0;
k=i+2;}}

将开始位置设为1,不断比较sum,如果出现了更大的sum则替换,并且尾位置i+1,如果sum<0则前面可以全部替换掉,新的位置则从i+2开始,如此循环,最后得到连续子序列问题的最大值。
还有其他要求输入开始和末尾两个位置的元素值,先记录下位置在用数组输出即可。
(2) 连续子序列和绝对值的最小值

for(j=1;j<=n;j++)
{ for(k=j;k<=n;k++)
{ temp[j]=temp[j]+a[k];
if(abs(temp[j]<sum[j])
sum[j]=abs(temp[j]);}}
for(j=1;j<=n;j++)
{ if(sum[j]<min) {min=sum[j];}}

用两重循环记录从j=1位置开始一直向后所有位置的绝对和最小值,最后在整个循环里比较每个位置的绝对值和,找出总最小值(这题开始还看成了下降子序列的和绝对值的最小值,错了好几遍才发现题意理解错了)
2.上升或下降子序列问题
(1) 最长上升序列

sum[1]=1;
for(i=1;i<=n;i++)
{ temp=0;
for(j=1;j<i;j++)
{ if(a[i]>a[j])
{ if(temp<sum[j]) temp=sum[j];}}
sum[i]=temp+1;

从i位置向左推,如果i位置的值大于j位置的值,并且这个位置的上升子序列数大于保留的最大子序列数,则存下新的值,记录下来每个位置的上升子序列最多的元素数放在一个循环里比较,得出总的最大数。
(2)最大上升子序列的和

sum[1]=a[1];
temp[1]=a[1];
for(i=2;i<=n;i++)
{ for(j=1;j<i;j++)
{if(a[i]>a[j]) {temp[i]=sum[j]+a[i];
if(temp[i]>=sum[i]) sum[i]=temp[i];}
else {temp[i]=a[i]; if(temp[i]>sum[i]) sum[i]=temp[i];}
} }
for(i=1;i<=n;i++) {if(sum[i]>=max) max=sum[i];}

思路基本和上题一样,都是从i往左推,如果满足上升条件,加了该位置的值是j循环里和最大的,保留该值,下次将这个值加到下一个方程中,如果不满足上升条件,则记录这一点的值和之前的和比较,两种大情况合起来求出i位置的最大值,最后用一个循环计算出所有i位置中拥有最大上升子序列和的值。

3.最长公共子序列

dp[0][0]=0
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
if(A[i]==B[j])
dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

两重循环代表两个子序列,如果两序列有字母相等,则相对【i-1】【j-1】的位置数量加一,通过dp使每一个满足元素相等的位置加一,如果两位置元素不相等,则这一点的最大值是由【i-1】【j】,【i】【j-1】位置中最大的推来的,两个dp方程共同推进,最后的dp【n】【m】就一定是最长公共子序列

二、一些比较有启发的dp题
1.Employment Planning
大意为输入一个值代表月份,第二行输入3个数(雇佣工人花费,工人工资,解雇工人花费),第三行表示每个月份需要的最小工人人数,到0停止循环,最后输出总花费的最小值。

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
int dp[20][1000];
int nu[20];
int main()
{
    int n,i,j,k;
    while(cin>>n&&n)
    { int a,b,c,maxx=0;
    cin>>a>>b>>c;
    for(i=1;i<=n;i++)
    { cin>>nu[i];maxx=max(maxx,nu[i]);}
    memset(dp,1,sizeof(dp));
    for(i=nu[1];i<=maxx;i++)
    { dp[1][i]=a*i+b*i;}
    for(i=2;i<=n;i++)
    { for( j=nu[i];j<=maxx;j++)
    { for(k=0;k<=maxx;k++)
    { if(k<=j) dp[i][j]=min(dp[i][j],dp[i-1][k]+(j-k)*a+j*b);
    else dp[i][j]=min(dp[i][j],dp[i-1][k]+(k-j)*c+j*b);}}}
    int minm=999999;
    for(i=1;i<=maxx;i++)
    { minm=min(dp[n][i],minm);}
    cout<<minm<<endl;}


}

i代表月份j代表保留人数构建dp方程,首先用循环求出所给月份中最大的所需工人人数作为上限,求出第一个月份中保留的最少人数到上限人数所需的花费以便保留人数的状态转移,使用dp方程,当这个月要去额外雇佣人时,用dp【i-1】【k】+(j-k)a+jb和dp【i】【j】相比较得出这个月保留j个人所需的最小花销,使用dp方程,如果这个月要解雇工人时,用dp【i】【j】,dp【i-1】【k】+(k-j)c+jb比较是否要解雇工人,解雇多少才能得出最小花费,最后总的累计出到n月时保留j个人的所有最小花费,再用一个循环求出最优解。
这道题其实映射了很多dp题目,他们都是先求出部分的最优解,在放到循环里比较这些得出总的最优解,和之前的子序列问题的解法并没有大的差别,只是状态比较麻烦,难以想好。
2.Flowers
题目大意为吃花,一份红花一份百花,红花没有要求,但是白花一次至少要吃掉k朵,给出一个区间,问在这个区间内吃花方案的总和

#include <iostream>
#include <algorithm>
#include <string.h>
const int maxn=1e5+5;
typedef long long l;
const l mod=1e9+7;
using namespace std;
l dp[maxn];
l sum[maxn];
int main()
{
     int i,j;
     l g,h;
     l a,b;
     cin>>g>>h;
     for(i=0;i<h;i++)
     { dp[i]=1;}
     for(i=h;i<=maxn;i++)
     { dp[i]=dp[i-1]+dp[i-h];
     }
     for(i=1;i<=maxn;i++)
     { sum[i]=sum[i-1]+dp[i];}
     for(i=0;i<=h;i++)
     { cin>>a>>b;
      cout<<(sum[b]-sum[a-1]+mod)%mod<<endl;}

}

这题的状态转移方程并不复杂,但是有些难想出来,只吃一朵花时只有红这一种可能,两朵时则有红红和白白两种可能,三朵时有红红+红,红+白白,白白+红,其中两种情况是红红和白白后面加的,即两朵时的方案数,一种是红加而得,即为i-k时的方案数,所以转移方程为dp【i】=dp【i-1】+dp【i-k】,因为这个题的数据可能较大,需要求1e9+7的Mod的值
通过typedef定义一个long long l的新类型,下面用到的具有相同数据类型和长度的变量可以直接用l定义,一定程度上简化了代码
三、区间dp入门
区间dp就是在区间上进行动态规划,求出区间上的最优解。通过合并小区间最优解进而求出整个大区间上的最优解

for(i=1;i<=n;i++)
{ dp[i][i]=//初始值}
for(l=2;l<=n;l++) 
 for(i=1;i<=n;i++)
{ int j=i+l-1;
if(j>n) break;}

取下i为区间起点,j为区间终点,然后根据实际问题构造状态转移方程
四、感想
经过几个周dp的学习和练习,我能明显的感受到普通dp题身上的共同点,在接下来的题中也有了一些思考方向,但是构建动态规划方程仍然是一件十分棘手的事情,不同的问题也有自己独特的复杂之处,仅仅一些dp题的练习只能使我明白大致方向,想要精准、正确的构建dp方程还需要很多的积累,很多的思考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值