ACM動態規劃模板(更新ing...)

  • 最長上升子序列問題
  • 循環數組最大子段和問題
  • 正整數分組問題
  • 多重揹包問題
  • 多重部分和問題
  • 劃分數問題
  • 多重集組合數問題
  • 最大子矩陣和問題
  • 數位dp問題

1、最長上升子序列問題

題目:有一個長爲n的數列a0,a1,…,an-1。請求出這個序列中最長的上升子序列的長度。上升子序列指的是對於任意的 i< j 都滿足ai< aj 的子序列。
思路: 定義dp[i]爲長度爲i+1的上升子序列中末尾元素的最小值(不存在的話爲INF)

//最長上升子序列問題
int dp[Max_n];

void solve(){
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<n;i++)
        *lower_bound(dp,dp+n,a[i])=a[i];
    printf("%d\n",lower_bound(dp,dp+n,inf)-dp);
}

1、循環數組最大子段和問題

題目: N個整數組成的循環序列a[1],a[2],a[3],…,a[n],求該序列如a[i]+a[i+1]+…+a[j]的連續的子段和的最大值。當所給的整數均爲負數時和爲0。(2 ≤N ≤ 50000,-10^9 ≤ a[i]≤10^9)
思路: 分情況討論:1. 最優的最大字段和在中間部分。2.最優的最大字段和在首尾兩端,此時中間部分是個最小字段和,用sum-中間部分最小字段和即可得到。

//循環數組最大子段和問題
int n;
int a[Max_n];
ll sum=0,Max=0,Min=0;

void solve(){
    ll t1=0,t2=0;
    for(int i=0;i<n;i++){
        if(t1>0)t1+=a[i];
        else t1=a[i];
        if(t1>Max)Max=t1;

        if(t2<0)t2+=a[i];
        else t2=a[i];
        if(t2<Min)Min=t2;
    }
    printf("%I64d\n",max(Max,sum-Min));
}

2、正整數分組問題

題目:將一堆正整數分爲2組,要求2組的和相差最小。例如:1 2 3 4 5,將1 2 4分爲1組,3 5分爲1組,兩組和相差1,是所有方案中相差最少的。(N ≤100, 所有正整數的和≤10000)
思路: 重量和價值都相等的01揹包變形。定義dp[i][j]表示爲從前i個數中,總和不超過j的最大值。

//正整數分組問題
int n,s[110];
int dp[10010];

void solve(){
    memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            for(int j=sum/2;j>=s[i];j--)
                dp[j]=max(dp[j],dp[j-s[i]]+s[i]);
        printf("%d\n",sum-2*dp[sum/2]);
}

3、多重揹包問題

題目:有n種重量、價值和數量分別爲wi,vi,ci的物品,從這些物品中挑選出總重量不超過W的物品,求出挑選物品價值總和的最大值。(1≤n≤100,1≤W≤50000)
思路:二進制優化多重揹包。

//多重揹包問題
int n,W;
int w[Max_n],v[Max_n],c[Max_n]; //重量、價值和數量
int dp[Max_W];

void ZeroOne_Pack(int w,int v){ 
    for(int i=W;i>=w;i--)
        dp[i]=max(dp[i],dp[i-w]+v);
}
void Complete_Pack(int w,int v){ 
    for(int i=w;i<=W;i++)
        dp[i]=max(dp[i],dp[i-w]+v);
}
int  Multi_Pack(){ 
    memset(dp,0,sizeof(dp));
    for(int i=0;i<n;i++){
        if(w[i]*c[i]>=W)Complete_Pack(w[i],v[i]);
        else {
            int k=1;
            while(k<c[i]){
                ZeroOne_Pack(w[i]*k,v[i]*k);
                c[i]-=k;
                k<<=1;
            }
            ZeroOne_Pack(w[i]*c[i],v[i]*c[i]);
        }
    }
    return dp[W];
}

4、多重部分和問題

題目:有n種不同大小的數字ai,每種各ci個,判斷是否可以從這些數字之中選出若干使它們的和恰好爲k。(1 ≤n≤100 , 1≤K≤100000)
思路: 定義dp[i+1][j]爲前 i 種數加和得到 j 時第 i 種數最多能剩餘多少(不能加和得到 i 的情況爲 -1)

//多重部分和問題
int n,k;
int a[Max_n],c[Max_n];
int dp[Max_k];

bool solve(){
    memset(dp,-1,sizeof(dp));
    for(int i=0;i<n;i++){
        dp[0]=c[i];
        for(int j=1;j<=k;j++){ //遞推關係
            if(dp[j]>=0)dp[j]=c[i];
            else if(j>=a[i]&&dp[j-a[i]]>0)dp[j]=dp[j-a[i]]-1;
            else dp[j]=-1;
        }
    }
    if(dp[k]>=0)return true;
    else return false;
}

5、劃分數問題

題目:有n個無區別的物品,將它們劃分成不超過m組,求出劃分方法數模M的餘數。(1 ≤m≤n≤1000)
思路: 考慮n的m劃分ai(i=1,2,3…),如果對於每個ai>0,那麼{ai-1}就對應了n-m的m劃分,另外如果存在ai=0,那麼就對應了n的m-1劃分。定義dp[i][j]爲 i 的 j 劃分的總數,則dp[i][j]=dp[i][j-1]+dp[i-j][j]。

//劃分數問題
int n,m;
int dp[Max_n][Max_m];

void solve(){
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=0;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i>=j)dp[i][j]=(dp[i][j-1]+dp[i-j][j])%M;
            else dp[i][j]=dp[i][j-1];
        }
    }
    printf("%d\n",dp[n][m]);
}

6、多重集組合數問題

題目:有n種物品,第i種物品有ai個。不同種類的物品可以互相區分但相同種類的無法區分。從這些物品中取出m個的話,有多少種取法?求出方案數模M的餘數。(1≤n≤1000,1≤m≤1000,1≤ai≤1000,2≤M≤10000)
思路: 定義dp[i][j]爲從前i中物品中取出j個的組合總數。
1. 當j≤a[i]時,dp[i][j]=dp[i-1][j]+dp[i-1][j] 2. 當j>s[i]時,dp[i][j]=dp[i-1][j]+dp[i-1][j]-dp[i-1][j-1-a[i]]。

//多重集組合數問題
int n,m;
int dp[2][Max_n];

void solve(){
    memset(dp,0,sizeof(dp));
    dp[0][0]=dp[1][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j<=a[i])dp[i&1][j]=(dp[i&1][j-1]+dp[(i-1)&1][j])%mod;
            else dp[i&1][j]=(dp[i&1][j-1]+dp[(i-1)&1][j]-dp[(i-1)&1][j-1-a[i]]+mod)%mod;
            //在有取餘的情況下,要避免減法運算的結果出現負數
        }
    }
    printf("%d\n",dp[n&1][m]);
}

7、最大子矩陣和問題

題目:一個N*M的矩陣,找到此矩陣的一個子矩陣,並且這個子矩陣的元素的和是最大的,輸出這個最大的值。如果所有數都是負數,就輸出0。(2 <= N,M <= 500)
思路: 最後的子矩陣一定在某兩行之間,枚舉所有1<=i<=j<=N,表示最終子矩陣選取的行範圍。分別求出第i行到第j行之間的每一列的和,第i行到第j行之間的最大子矩陣和對應於這個和數組的最大子段和。

//最大子矩陣和問題
int n,m;
int sum[Max_m];
int map[Max_n][Max_m];

void solve(){
    int Max=0;
    for(int i=1;i<=n;i++){
        memset(sum,0,sizeof(sum));
        for(int j=i;j<=n;j++){
            for(int k=1;k<=m;k++)sum[k]+=map[j][k];
            int ans=0;
            for(int k=1;k<=m;k++){ //求子矩陣的最大字段和
                if(ans>=0)ans+=sum[k];
                else ans=sum[k];
                if(ans>Max)Max=ans;
            }
        }
    }
    printf("%d\n",Max);
}

7、數位dp問題

思路: dp思想,枚舉到當前位置pos,狀態爲state(這個就是根據題目來的,可能很多,畢竟dp千變萬化)的數量(既然是計數,dp值顯然是保存滿足條件數的個數)

typedef long long ll;  
int a[20];  
ll dp[20][state];//不同題目狀態不同  
ll dfs(int pos,/*state變量*/,bool lead/*前導零*/,bool limit/*數位上界變量*/)//不是每個題都要判斷前導零  
{  
    //遞歸邊界,既然是按位枚舉,最低位是0,那麼pos==-1說明這個數我枚舉完了  
    if(pos==-1) return 1;/*這裏一般返回1,表示你枚舉的這個數是合法的,那麼這裏就需要你在枚舉時必須每一位都要滿足題目條件,也就是說當前枚舉到pos位,一定要保證前面已經枚舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */  
    //第二個就是記憶化(在此前可能不同題目還能有一些剪枝)  
    if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];  
    /*常規寫法都是在沒有限制的條件記憶化,這裏與下面記錄狀態是對應,具體爲什麼是有條件的記憶化後面會講*/  
    int up=limit?a[pos]:9;//根據limit判斷枚舉的上界up;這個的例子前面用213講過了  
    ll ans=0;  
    //開始計數  
    for(int i=0;i<=up;i++)//枚舉,然後把不同情況的個數加到ans就可以了  
    {  
        if() ...  
        else if()...  
        ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變量傳參都是這樣寫的  
        /*這裏還算比較靈活,不過做幾個題就覺得這裏也是套路了 
        大概就是說,我當前數位枚舉的數是i,然後根據題目的約束條件分類討論 
        去計算不同情況下的個數,還有要根據state變量來保證i的合法性,比如題目 
        要求數位上不能有62連續出現,那麼就是state就是要保存前一位pre,然後分類, 
        前一位如果是6那麼這意味就不能是2,這裏一定要保存枚舉的這個數是合法*/  
    }  
    //計算完,記錄狀態  
    if(!limit && !lead) dp[pos][state]=ans;  
    /*這裏對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裏就是lead就完全不用考慮了*/  
    return ans;  
}  
ll solve(ll x)  
{  
    int pos=0;  
    while(x)//把數位都分解出來  
    {  
        a[pos++]=x%10;//個人老是喜歡編號爲[0,pos),看不慣的就按自己習慣來,反正注意數位邊界就行  
        x/=10;  
    }  
    return dfs(pos-1/*從最高位開始枚舉*/,/*一系列狀態 */,true,true);//剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視爲0嘛  
}  
int main()  
{  
    ll le,ri;  
    while(~scanf("%lld%lld",&le,&ri))  
    {  
        //初始化dp數組爲-1,這裏還有更加優美的優化,後面講  
        printf("%lld\n",solve(ri)-solve(le-1));  
    }  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章