每日3題(March,21,2019)

吾日三省吾身,刷題否,刷題否,刷題否

目錄:

  • 1.最小花費(動態規劃/Dikjstra最短路)
  • 2.放蘋果(動態規劃)

1.一省

1.1題目描述

在某條線路上有N個火車站,有三種距離的路程,L1,L2,L3,對應的價格爲C1,C2,C3.其對應關係如下: 距離s :票價 0<S<=L1 : C1 L1<S<=L2:C2 L2<S<=L3:C3 輸入保證0<L1<L2<L3<10910^9,0<C1<C2<C3<10910^9。 每兩個站之間的距離不超過L3。 當乘客要移動的兩個站的距離大於L3的時候,可以選擇從中間一個站下車,然後買票再上車,所以乘客整個過程中至少會買兩張票。 現在給你一個 L1,L2,L3,C1,C2,C3。然後是A B的值,其分別爲乘客旅程的起始站和終點站。 然後輸入N,N爲該線路上的總的火車站數目,然後輸入N-1個整數,分別代表從該線路上的第一個站,到第2個站,第3個站,……,第N個站的距離。 根據輸入,輸出乘客從A到B站的最小花費。
輸入描述:
以如下格式輸入數據:
L1 L2 L3 C1 C2 C3
A B
N
a[2]
a[3]
……
a[N]
輸出描述:
可能有多組測試數據,對於每一組數據,
根據輸入,輸出乘客從A到B站的最小花費。
示例1
輸入
1 2 3 1 2 3
1 2
2
2
輸出
2

1.2基本思路

主要與兩種思路:
我一開始想到的是第一種思路:首先根據分段函數通過兩個結點之間的距離計算出相鄰的結點之間的代價,然後將結點之間的代價作爲權值利用鄰接鏈表進行存儲,隨後得到圖的完整表示。最後利用Dijkstra最短路算法求解得到結點a和b之間的最短路徑長度。

這道題想要少寫些代碼快速的解決掉,還是得利用動態規劃。由於a≤b,那麼其實可以定義規劃數組dp[N],其中dp[i]表示從a到結點i的最小花費。可以得到以下遞推關係式:
dp[i]=minaji{dp[j]+cost&lt;j,i&gt;} dp[i]=\min\limits_{a≤j≤i}\{{dp[j]+cost&lt;j,i&gt;}\}
其中cost&lt;j,i&gt;cost&lt;j,i&gt;爲結點j到結點i之間的費用。不難發現,這個遞推關係式和最長遞增子序列問題有點像。

1.3代碼實現

Dijkstra

#include <iostream>
#include <climits>
#include <vector>
#define N 101
using namespace std;

struct E{
    int node;
    int cost;
};

int L[3],C[3];
int dis[N];
int shortest[N];//記錄結點a到結點b的最短距離
bool mark[N];
vector<E> V[101];//存儲圖的結構

int main()
{
    while(~scanf("%d%d%d%d%d%d",&L[0],&L[1],&L[2],&C[0],&C[1],&C[2])){
        int a,b,n;
        scanf("%d%d",&a,&b);
        scanf("%d",&n);
        dis[1]=0;
        for(int i=2;i<=n;i++){
            scanf("%d",&dis[i]);
        }
        for(int i=1;i<=n;i++){
            V[i].clear();//清空圖
        }
        E tmp;
        for(int i=1;i<=n;i++){//利用鄰接鏈表存儲圖的結構
            for(int j=i+1;j<=n;j++){
                if(dis[j]-dis[i]<=L[0]){
                    tmp.node=j;
                    tmp.cost=C[0];
                    V[i].push_back(tmp);
                    tmp.node=i;
                    V[j].push_back(tmp);
                }
                else if(dis[j]-dis[i]<=L[1]){
                    tmp.node=j;
                    tmp.cost=C[1];
                    V[i].push_back(tmp);
                    tmp.node=i;
                    V[j].push_back(tmp);
                }
                else if(dis[j]-dis[i]<=L[2]){
                    tmp.node=j;
                    tmp.cost=C[2];
                    V[i].push_back(tmp);
                    tmp.node=i;
                    V[j].push_back(tmp);
                }
                else
                    break;//一旦出現到不了的結點,後續的結點都無法到達
            }
        }
        //initial
        for(int i=1;i<=n;i++){
            mark[i]=false;
            shortest[i]=INT_MAX;
        }
        //Dijkstra
        int newP=a;//將起始點加入集合K
        shortest[newP]=0;
        mark[newP]=true;
        for(int k=1;k<=n-1;k++){//還剩下其餘的n-1個結點需要進行選擇
            for(int i=0;i<V[newP].size();i++){//遍歷所有與newP相鄰的結點
                if(mark[V[newP][i].node])
                    continue;
                if(shortest[newP]+V[newP][i].cost<shortest[V[newP][i].node]||shortest[V[newP][i].node]==INT_MAX){
                    shortest[V[newP][i].node]=shortest[newP]+V[newP][i].cost;
                }
            }
            int min=INT_MAX;
            for(int i=1;i<=n;i++){//查詢不屬於集合k的最近點
                if(mark[i]||shortest[i]==INT_MAX)continue;
                if(shortest[i]<min){
                    min=shortest[i];
                    newP=i;
                }
            }
            mark[newP]=true;
        }
        printf("%d\n",shortest[b]);
    }
    return 0;
}

動態規劃

#include <iostream>
#include <climits>
#define N 101
using namespace std;

int n,a,b;
int L[3],C[3];
int dis[N];
int dp[N];//dp[i]表示從a->i的最下花費

int getCost(int dist){
    if(dist<=L[0])return C[0];
    else if(dist<=L[1]) return C[1];
    else if(dist<=L[2]) return C[2];
}

int main()
{
     while(~scanf("%d%d%d%d%d%d",&L[0],&L[1],&L[2],&C[0],&C[1],&C[2])){
        scanf("%d%d",&a,&b);
        scanf("%d",&n);
        for(int i=1;i<=n;i++)dp[i]=INT_MAX;
        dis[1]=0;
        dp[a]=0;
        for(int i=2;i<=n;i++)
            scanf("%d",&dis[i]);
        for(int i=a;i<=b;i++){
            for(int j=a;j<=i;j++){
                if(dis[i]-dis[j]<=L[2]){//表示i與j之間連通
                    if(dp[j]+getCost(dis[i]-dis[j])<dp[i])
                        dp[i]=dp[j]+getCost(dis[i]-dis[j]);
                }
            }
        }
        printf("%d\n",dp[b]);
     }
    return 0;
}

2.二省

2.1題目描述:

把M個同樣的蘋果放在N個同樣的盤子裏,允許有的盤子空着不放,問共有多少種不同的分法?(用K表示)5,1,1和1,5,1 是同一種分法。
輸入描述:
每行均包含二個整數M和N,以空格分開。1<=M,N<=10。
輸出描述:
對輸入的每組數據M和N,用一行輸出相應的K。
示例1
輸入
7 3
輸出
8

2.2基本思路

對於該問題需要採用動態規劃的思想進行求解,一開始思路打偏了,一直想用排列組合的方法,想了好久一直找不到一個合適的解決方案。
以下爲動態規劃的思路:

dp(m,n)表示m個蘋果,n個盤子的方法。但m<n時,即至多隻有m個盤子裏面有蘋果,去掉剩餘的盤子也沒關係,所以爲dp(m,m)。當m≥n時,存在兩種情況①至少存在一個空盤子此時返回dp(m,n-1)②不存在空盤子,那麼當前的狀態與每個盤子上面都拿掉一個蘋果等價,即此時返回F(m-n,n).綜上,m≥n時dp(m,n)=dp(m,n-1)+dp(m-n,n); 最後考慮初始化的過程,可以明顯的知道,當m0或者n=1的時候都只有一種情況。該問題可以採用遞歸和迭代兩種方法進行求解。綜上所述可以得到以下遞推方程:
dp[n][m]={1,m=0 or n=1dp[m][m],m&lt;ndp[m,n1]+dp[mn,n],mn dp[n][m]= \begin{cases} 1, m=0\ or \ n=1\\ dp[m][m],m&lt;n\\ dp[m,n-1]+dp[m-n,n],m≥n\\ \end{cases}

2.3代碼實現

#include <iostream>
#define N 11
using namespace std;

int F(int m,int n){//遞歸的求解子問題
    if(m==0||n==1)//注意題目雖然說了M≥1,但遞歸出口出必須定義m==0的情況
        return 1;
    else if(m<n){
        return F(m,m);
    }
    else
        return F(m,n-1)+F(m-n,n);
}

int main()
{
    int m,n;
    int dp[N][N];
    while(~scanf("%d%d",&m,&n)){
        for(int i=0;i<=n;i++)dp[0][i]=1;
        for(int i=0;i<=m;i++)dp[i][1]=1;

        for(int i=1;i<=m;i++){//採用迭代的方法進行求解
            for(int j=2;j<=n;j++){//注意此處n應該從2開始
                if(i<j)
                    dp[i][j]=dp[i][i];
                else
                    dp[i][j]=dp[i][j-1]+dp[i-j][j];
            }
        }
        printf("%d\n",dp[m][n]);
    }
    return 0;
}

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