學習總結 線性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以避免不必要的麻煩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章