1. 程式人生 > >Luogu_2876_[USACO07JAN]解決問題Problem Solving

Luogu_2876_[USACO07JAN]解決問題Problem Solving

題目描述

過去的日子裡,農夫John的牛沒有任何題目. 可是現在他們有題目,有很多的題目.

精確地說,他們有\(P(1 \leq P \leq 300)\)道題目要做. 他們還離開了農場並且象普通人一樣找到了工作. 他們的月薪是\(M(1 \leq M \leq 1000)\) 元.

他們的題目是一流的難題,所以他們得找幫手.幫手們不是免費的,但是他們能保證在一個月內作出任何題目.
每做一道題需要兩筆付款, 第一筆\(A_i(1 \leq A_i \leq M)\)元在做題的那一個月初支付, 第二筆\(B_i\)\((1 \leq B_i \leq M)\)在做完後的下一個月
初支付. 每一個月牛們用上一個月掙的錢來付款. 牛沒有任何存款意識, 所以每個月的節餘都回拿用去買糖吃掉了.

因為題目是相互關連的,它們必須按順序解出. 比如,題目3必須在解題目4之前或同一個月解出.

找出牛們做完所有題目並支付完所有款項的最短月數.

輸入輸出格式

輸入格式

第一行: N 和 P;
第2...P+1行: 第i行包含A_i和B_i, 分別是做第i道題的欲先付款和完成付款.

輸出格式

一行: 牛們做完題目和付完帳目的最少月數.

樣例

INPUT

100 5
40 20
60 20
30 50
30 50
40 40

OUTPUT

6

HINT

SOLUTION

dp(應該算區間dp吧qwq)

第一眼看這題怎麼亂七八糟的,為什麼這個月的決策會影響到下個月的決策啊,這不是後效性嗎,那怎麼可能是dp啊。

於是瞎模擬亂寫。於是不會。

到網上找了官方題解。。先貼個連結吧qwq
「官方題解傳送門」

所以這裡我用的就是官方題解的第一種解法:\(O(n^3)\)的解法。

首先我們的\(dp\)陣列開兩維,\(dp[i][j]\)表示完成從第\(i\)題到第\(j\)題所用的最少月份數。
然後我們就從\(dp[k][i-1]\space (1 \leq k<i)\)轉移而來,再就是判定一下一個月能不能做完不能就空一個月,能做完就接上直接+1,然後再所有合法的情況中取\(min\)值就好了。

所以這應該算區間dp吧qwq。

其實這題這麼一說看上去好像真的沒啥難度,但是當時看那個亂七八糟的支付規則我還真沒想到直接這麼轉移就好了。這也算挺好的一題吧。

我的程式碼和官方的程式碼不一樣qwq:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
#define Max(a,b) ((a>b)?a:b)
#define Min(a,b) ((a<b)?a:b)
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {x=x*10+ch-48;ch=getchar();}
    return x*f;}
const int N=310;
int n,m,dp[N][N],fst[N],snd[N],sum1[N],sum2[N];
inline int jud(int l,int mid,int r){
    int cnt=sum2[mid-1]-sum2[l-1]+sum1[r]-sum1[mid-1];return (cnt<=m);}
inline int jud2(int l,int r){
    int cnt=sum1[r]-sum1[l-1];if (cnt>m) return 0;
    cnt=sum2[r]-sum2[l-1];if (cnt>m) return 0;
    return 1;}
int main(){
    int i,j;
    m=read();n=read();sum1[0]=0;sum2[0]=0;
    for (i=1;i<=n;++i) {fst[i]=read();sum1[i]=sum1[i-1]+fst[i];
        snd[i]=read();sum2[i]=sum2[i-1]+snd[i];}
    memset(dp,0x3f,sizeof(dp));
    for (i=1;i<=n;++i) if (jud2(1,i)) dp[1][i]=2;else break;
    for (i=2;i<=n;++i){
        for (j=i;j<=n;++j){
            if (!jud2(i,j)) continue;
            for (int k=1;k<i;++k){
                if (jud(k,i,j)) dp[i][j]=Min(dp[i][j],dp[k][i-1]+1);
                else if (jud2(k,i-1)) dp[i][j]=Min(dp[i][j],dp[k][i-1]+2);
            }
        }
    }
    int ans=2*n+1;
    for (i=1;i<=n;++i) ans=Min(ans,dp[i][n]);
    printf("%d\n",ans+1);
    return 0;
}