学习总结 线性dp的做题经历

题目
1 hdu 1260
天哪,多棒的电影啊!成千上万的人正在赶往电影院。然而,对于卖电影票的乔来说,这真的是一个非常糟糕的时刻。他徘徊在什么时候才能尽早回家。一个很好的方法,减少门票销售的总时间,是让相邻的人一起买票。由于售票机的限制,乔一次可以卖出一张或两张相邻的票。因为你是伟大的耶稣,你知道每个人为他/她买一张或两张票需要多少时间。你能告诉可怜的乔几点能早点回家吗?如果是这样的话,我想乔会很感激你的帮助。
有N(1<=N<10)不同的场景,每个场景由3行组成:1)表示总人数的整数K(1<=K<=2000);2)K个整数数(s<=Si<=25s),表示每个人买票所需的时间;3)(K-1)整数号(0<=Di<=50),表示两个相邻的人一起购买两张票所需的时间。

解决思路,这题的解决较为容易,按人分阶段,f[i] 表示第i个人买票后所耗费的时间,由于相邻的两个人可以共同买票,所以f[i]有两种表现形式,f[i-1]+a[i],
f[i-2]+ti[i],取这两个的较小值。

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
    int n;
    scanf("%d",&n);
    int a[2005];
    int f[2005];
    int ti[2005];
    while(n--)
    {
        int k;
        scanf("%d",&k);
        for(int i=1;i<=k;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(f,0,sizeof(0));
        memset(ti,0,sizeof(0));
        f[1]=a[1];
        for(int i=2;i<=k;i++)
        {
            scanf("%d",&ti[i]);
        }
        for(int i=2;i<=k;i++)
        {
            f[i]=min(f[i-1]+a[i],f[i-2]+ti[i]);
        }
        int k1=(8+f[k]/3600)%24;
        int k2=(f[k]%3600)/60;
        int k3=f[k]%60;
        if(k1<=12) printf("%02d:%02d:%02d am\n",k1,k2,k3);
        else printf("%02d:%02d:%02d pm\n",k1,k2,k3);
    }
    return 0;
}

这里有一点要注意,%24很容易忘。一定要看准输出形式,am,和pm要把握一下。

2 hdu 1158
项目经理想要确定每个月需要多少工人。他确实知道每个月所需的最低工人人数。当他雇用或解雇一名工人时,会有一些额外的费用。一旦一个工人被录用,即使他不工作,他也能拿到工资。经理知道雇用一名工人、解雇一名工人的费用和一名工人的工资。然后,经理将面临这样一个问题:为了保持项目的最低总成本,他每月要雇佣多少工人或解雇多少工人。
输入可以包含多个数据集。每个数据集包含三行。第一行包含计划使用的项目月数,不超过12个月;第一行包含雇用工人的费用、工资数额、解雇工人的费用。第三行包含几个数字,表示每个月所需的最小工人数。输入以包含单个“0”的行结束。

题目思路
这道题目没给出数据范围,而且很难想,如果按月分阶段,求出到第i个月是所耗费的最小费用的话,写着写着就会发现很不对劲,因为最终的总的费用不能是按每一阶段的最小费用来求,这是贪心的思想,但是,贪心的局限性在于不能涵盖全局,第i个月时最小,下一个月反而有可能更大,炒掉的人多,雇佣的人也多,雇佣费大于节省的费用就会出现这样的结果。那么,怎样才能使之全面呢?阶段点是月份,那么这个点可以对应多种状态,在保证工人够用的情况下,工人个数还可以更多,那么所有月的最大的工人数就是状态的上线,下线就是当前月所需要的最小工人数,这时,f[i][j]表示第i个月,雇佣j名所花费的钱数,由于每一种状态都与前一个月的多种状态有关,所以月份循环,当前月循环,之前月循环,三重循环,多组测试数据,若不超时,工人数目不会很多,多说1000个,几百个正合适。之后就是找最小值,先找出第n个月雇佣a[n]到max(工人)的各个最小值,再找出这些值中的最小值。

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
    int n;
    int a[15],f[15][505];
    while(~scanf("%d",&n))
    {
        if(n==0) break;
        int hi,fi,slr;
        scanf("%d%d%d",&hi,&slr,&fi);
        int ma=0;
       // int mi=0x3f3f3f3f;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            ma=max(a[i],ma);
           // mi=min(a[i],mi);
        }
        memset(f,0x3f,sizeof(f));
        for(int i=a[1];i<=ma;i++)
        {
            f[1][i]=hi*i+i*slr;
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=a[i];j<=ma;j++)
            {
                int ti=0x3f3f3f3f;
                for(int fr=a[i-1];fr<=ma;fr++)
                {
                    int l=j-fr;
                    int t;
                    if(l>=0)
                    {
                         t=f[i-1][fr]+l*hi+j*slr;
                    }
                    else
                    {
                         t=f[i-1][fr]-l*fi+j*slr;
                    }
                    ti=min(ti,t);
                }
                f[i][j]=ti;
            }
        }
        int ans=0x3f3f3f3f;
        for(int i=a[n];i<=ma;i++)
        {
            ans=min(f[n][i],ans);
        }
        printf("%d\n",ans);
    }
    return 0;
}

3
自从见识了平安夜苹果的涨价后,Lele就在他家门口水平种了一排苹果树,共有N棵。

突然Lele发现在左起第P棵树上(从1开始计数)有一条毛毛虫。为了看到毛毛虫变蝴蝶的过程,Lele在苹果树旁观察了很久。虽然没有看到蝴蝶,但Lele发现了一个规律:每过1分钟,毛毛虫会随机从一棵树爬到相邻的一棵树上。

比如刚开始毛毛虫在第2棵树上,过1分钟后,毛毛虫可能会在第1棵树上或者第3棵树上。如果刚开始时毛毛虫在第1棵树上,过1分钟以后,毛毛虫一定会在第2棵树上。

现在告诉你苹果树的数目N,以及毛毛刚开始所在的位置P,请问,在M分钟后,毛毛虫到达第T棵树,一共有多少种行走方案数。

题目思路
dfs bfs 2的100次方,搜索废了,但这题要想全面,还得把每个点都轮一遍,怎么办?靠!这时可以画图,路径问题,和迷宫差不多,到达一颗树,那么只能从其相邻的两个数过来,那么路径数就等于到达其相邻的两个数相加,然后时间为阶段,时间点要对应所有的树的状态,f[i][j]表示i时刻的第j棵树的状态,那么要到达这棵树只能由前一个时刻与其相邻的两颗数过来,f[i][j]=f[i-1][j+1]+f[i-1][j-1]。这道题如果出错还找不到问题的话,可能是memset()中的sizeof(f)给写错了。

#include <iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
int main()
{
    int n,p,m,t;
    int f[105][105];
    while(~scanf("%d%d%d%d",&n,&p,&m,&t))
    {
        memset(f,0,sizeof(f));
       f[0][p]=1;
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i-1][j+1]+f[i-1][j-1];
        }
        printf("%d\n",f[m][t]);
    }
}

4
给定N个整数的数组,找出其绝对值最小的连续子序列。
The first line contains a single integer T, indicating the number of test cases.
Each test case includes an integer N. Then a line with N integers Ai follows.

Technical Specification

  1. 1 <= T <= 100
  2. 1 <= N <= 1 000
  3. -100 000 <= Ai <= 100 000
    数据不大,但是如果这题这样做,以每个数为阶段,每个数对应两种状态,自己单独为一段,自己与之前的元素为一段,f[i]=min(f[i-1]+a[i],a[i]),这样是不对的,和第二个那个题一样,不全面,忽略了对后续的影响。
    失败代码
#include <iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
    int f[1005];
    int t;
    scanf("%d",&t);
    int a[1005];
    int p=0;
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(f,0,sizeof(f));
         f[1]=a[1];
        for(int i=2;i<=n;i++)
        {
            if(abs(a[i])>abs(f[i-1]+a[i]))
            {
                f[i]=f[i-1]+a[i];
            }
            else f[i]=a[i];
        }
        int ans=abs(f[1]);
        for(int i=1;i<=n;i++)
        {
            ans=min(ans,(int)abs(f[i]));
        }
        printf("Case %d: %d\n",++p,ans);
    }
    return 0;
}

正确的做法,以每个数为阶段的开始,向后推进,每推进一个算出和来与之前的比较,找出最小值就行了。

5
一到n,有n个数,将这些数分组,给你n的个数,问分法有多少种,每一组数的个数可为任意个。
如果用f[i]表示i个数分组所具有的方案数,那这个关系就很难找,但是如果把他拆的碎一些,
f[i][j] 表示i个数分为j组的方案数,最终结果就是f【n】【1到j】的和。
这时就可以列一个表,1到i为行标,表示分组数,1到j为列标,表示数的个数,
写出一些数据,就会发现规律了。

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
    long long f[26][26]={0};
    for(int i=1;i<=25;i++)
    {
        f[1][i]=1;
    }
    for(int j=2;j<=25;j++)
        for(int i=2;i<=25;i++)
    {
        f[i][j]=i*f[i][j-1]+f[i-1][j-1];
    }
    long long ans[26]={0};
    for(int j=1;j<=25;j++)
    {
        for(int i=1;i<=j;i++)
        {
            ans[j]+=f[i][j];
        }
    }
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        printf("%lld\n",ans[n]);
    }
    return 0;
}

6
Given a permutation a1, a2, … aN of {1, 2, …, N}, we define its E-value as the amount of elements where ai > i. For example, the E-value of permutation {1, 3, 2, 4} is 1, while the E-value of {4, 3, 2, 1} is 2. You are requested to find how many permutations of {1, 2, …, N} whose E-value is exactly k.
There are several test cases, and one line for each case, which contains two integers, N and k. (1 <= N <= 1000, 0 <= k <= N).
Output one line for each case. For the answer may be quite huge, you need to output the answer module 1,000,000,007.
There is only one permutation with E-value 0: {1,2,3}, and there are four permutations with E-value 1: {1,3,2}, {2,1,3}, {3,1,2}, {3,2,1}

题目大意 给定n,给定k,k为逆序数,求n个数有多少种排列方案满足有k个逆序数。
思路
把他拆碎,以每个数为阶段,对应状态为不同的逆序数,i个数,j为其逆序,f[i][j]为其方案数。
这时就列一个表 1到1000行数, 一到1000列数,找规律,每增加一个新数时,有两种想法,一种是在原有的排列中插空,一种是放入末尾,与原有的数进行交换,由于这题是在求逆序,显然交换这种想法更容易找到规律。如果用插空的想法,就是晕晕晕。用交换的思路,写出一些数据,列举一些简单的列子,规律就出来了。

7
FatMouse believes that the fatter a mouse is, the faster it runs. To disprove this, you want to take the data on a collection of mice and put as large a subset of this data as possible into a sequence so that the weights are increasing, but the speeds are decreasing.
Input contains data for a bunch of mice, one mouse per line, terminated by end of file.

The data for a particular mouse will consist of a pair of integers: the first representing its size in grams and the second representing its speed in centimeters per second. Both integers are between 1 and 10000. The data in each test case will contain information for at most 1000 mice.

Two mice may have the same weight, the same speed, or even the same weight and speed.

Your program should output a sequence of lines of data; the first line should contain a number n; the remaining n lines should each contain a single positive integer (each one representing a mouse). If these n integers are m[1], m[2],…, m[n] then it must be the case that

W[m[1]] < W[m[2]] < … < W[m[n]]

and

S[m[1]] > S[m[2]] > … > S[m[n]]

In order for the answer to be correct, n should be as large as possible.
All inequalities are strict: weights must be strictly increasing, and speeds must be strictly decreasing. There may be many correct outputs for a given input, your program only needs to find one.
6008 1300
6000 2100
500 2000
1000 4000
1100 3000
6000 2000
8000 1400
6000 1200
2000 1900

4
4
5
9
7
题目思路 最长上升子序列的升级版,排序后再找最长上升子序列,但是,要输出路径,这时就需要加一个链子一样的东西fro[i]=j,i表示第i个数的下表,j表示第i个数之前的那个与之组成上升序列的那个数的下表j,这样fro[i]=j,就关联了第i个数与第j个数。

#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct mou
{
    int weig,spe;
    int xu;
}m[1005];
bool cmp(mou mou1,mou mou2)
{
    if(mou1.spe==mou2.spe)
    {
        return mou1.weig<mou2.weig;
    }
    return mou1.spe>mou2.spe;
}
int f[1005];
int fro[1005];
int b[1005];
int main()
{
    int i=1;
    int wei;
    int sp;
    while(~scanf("%d%d",&wei,&sp))
    {
        m[i].xu=i;
        m[i].weig=wei;
        m[i].spe=sp;
        i++;
    }
    sort(m+1,m+i,cmp);
    int n=i-1;
    f[1]=1;
    for(int i=2;i<=n;i++)
    {
        int cns=0;
        for(int j=1;j<i;j++)
        {
            if(m[i].weig>m[j].weig&&m[i].spe<m[j].spe)
            {
                if(cns<f[j])
                {
                    cns=f[j];
                    fro[i]=j;
                }
            }
        }
        f[i]=cns+1;
    }
    int ans=0;
    int k;
    for(int j=1;j<=n;j++)
    {
        if(ans<f[j])
            ans=f[j],k=j;
    }
    printf("%d\n",ans);
    int j=1;
    while(k!=0)
    {
        b[j++]=m[k].xu;
        k=fro[k];
    }
    for(int r=j-1;r>=1;r--)
    {
        printf("%d\n",b[r]);
    }
    return 0;
}

8
给一个长度位 3 * n 的序列a,删除 n 个数,形成一个长度位2 * n的新序列a’(顺序未改变).并使新的序列前n个数的和 - 后n个数的和最大。

这道题要按照区域来分,分成2n个区域,1到n+1删1个数与n+2到3n删n-1个数,一到n+2删两个数与n+3到3n删n-2个数,。。。。每两个区域之间求差值,然后再众多差值中找最值。
可以设置两个优先队列,一个解决第一组的求大值的问题,一个解决第二组数求小值的问题,n+1到2n的数负责与两边比较,进行修改,f1[i],表示n+1到2n中的前i个数参与修改后的值,那么与之对应的就是f2[i+1]表示n+1到2n中的i+1到2n个数参与修改后的值,然后再在f1[i]-f2[i+1]的多个值中找到最值,就是最终我们要的结果。优先队列的使用使得找大值和小值的过程变得简单,代码写起来也相对较为容易,并且,优先队列找大值与小值的速度较快。
总结
动态规划的好处是全面,把一个问题分成若干个节点,由于对每个节点的各种状态都做了把握,所以最终的结果往往漏洞较少,有些问题看似可以贪心,但是往往会出现不全面的问题。
有一些问题看似是新问题,实际上是老问题的派生,找一找新问题与老问题的相似之处,会有不错的结果。
对于求方案数的问题,往往需要把一个节点在拆成若干个部分,然后再将这个节点的各个部分相加到一起。
问题的节点找对了,找出节点到底有多少状态是解决问题的关键,比如2题,有时往往不是状态转移关系难写,而是节点所对应的状态没有找全,找全了,转移关系似乎就能明白不少了。
优先队列是一个提速较快的东西,可以省去求最值的繁琐的过程,节省时间。
cin.tie(0),ios::sync_with_stdio(0);可以提升cin ,cout 的速度,对于scanf(“%c”)输入字符时,由于scanf输入字符会遇到比较多的麻烦,可以用此来替代scanf以避免不必要的麻烦。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值