DP進階

EOJ 1051 完全加括號的矩陣連乘積


n 個矩陣的矩陣鏈A1,A2,,An ,矩陣 Ai 的規模爲pi1pi(1in)AiAi+1 是相容可乘的,求最優的完全加括號方案,使得依此次序計算矩陣鏈乘積:i=1nAi 所需要的乘法次數最少。

說明


顯然是動態規劃問題,用dp[i][j]表示從AiAj 的最少乘法次數,不難得到狀態轉移方程

dp[i][j]={0i=jmin{dp[i][k]+dp[k+1][j]+p[i1]p[k]p[j]}i<=k<j

由此可以寫出遞歸形式:
#include<bits/stdc++.h>
#define INF 4611686018427387904
#define minn(a, b) (a) <= (b) ? (a) : (b);
using namespace std;
typedef long long ll;

int a[60], b[60];
ll ans[60][60];

ll calc(int l, int r)
{
    if (ans[l][r] != -1) return ans[l][r];
    if (l == r - 1) return ans[l][r] = a[l] * b[l] * b[r];
    if (l == r) return ans[l][r] = 0;
    ll ret = INF;
    for (int j = l; j < r; ++j)
        ret = minn(ret, calc(l, j) + calc(j+1, r) + a[l] * b[j] * b[r]);
    return ans[l][r] = ret;
}

int main()
{
    int t, n;
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        for (int i = 0; i < n; ++i) scanf("%d%d", &a[i], &b[i]);
        memset(ans, -1, sizeof(ans));
        printf("%lld\n", calc(0, n-1));
    }
    return 0;
}

以及遞推形式(注意計算順序):

#include<stdio.h>
#define N 55
int m[N][N]={0};
int p[N];
int main()
{
    int i,j,k,r,t,e,n;
    scanf("%d",&e);
    while(e--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)scanf("%d%d",&p[i],&p[i+1]);
        for(i=1;i<=n;i++)m[i][i]=0;
        for(r=1;r<n;r++)
        {
            for(i=1;i<=n-r;i++)
            {
                j=i+r;
                m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
                for(k=i+1;k<j;k++)
                {
                    t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                    if(t<m[i][j])m[i][j]=t;
                }
            }
        }
        printf("%d\n",m[1][n]);
    }
    return 0;
}

EOJ 2921/USACO 2006 November Gold/POJ 3254/BZOJ 1725 Corn Fields


給出一個M*N的矩陣,元素爲0表示這個地方不能種玉米,爲1表示這個地方能種玉米,現在規定所種的玉米不能相鄰,即每行或者沒列不能有相鄰的玉米,問一共有多少種種植方法。

說明


第一次嘗試做狀壓dp……由於第i行的玉米狀態取決於第i-1行,因此爲了在狀態之間進行轉移,我們將每行上的玉米種植情況壓縮爲一個二進制數,其中1表示有玉米,0表示無玉米。之後運用位運算來判斷行列之間的相鄰情況。定義dp[i][j]爲使第i行狀態爲j的方案數。容易得到方程dp[i][j]=sum(dp[i-1][k])。

#include <cstdio>
#define mod 100000000
using namespace std;
int m, n, i, ans, maxn;
int mp[13], f[13][4096];

void dp()
{
    for(i = 0; i <= maxn; ++i)
        if(!(i & (i >> 1)) && (i | mp[1]) == mp[1])
            f[1][i] = 1;
    for(i = 2; i <= m; ++i)
        for(int j = 0; j <= maxn; ++j){
            if(f[i-1][j])
                for(int k = 0; k <= maxn; ++k)
                    if(!(j & k) && (k | mp[i]) == mp[i] && !(k & (k >> 1)))
                        f[i][k] = (f[i][k] + f[i-1][j]) % mod;
       }
    for(i = 0; i <= maxn; ++i) ans = (ans + f[m][i]) % mod;
}

int main()
{
    int tmp;
    scanf("%d%d", &m, &n);
    for(i = 1; i <= m; ++i)
        for(int j = 1; j <= n; ++j){
           scanf("%d", &tmp);
           mp[i] = (mp[i] << 1) + tmp;
        }
    maxn = (1 << n) - 1;
    dp();
    printf("%d", ans);
    return 0;
}

EOJ 1027 郵資的問題


有 n 種面額不同的郵票,面額分別爲 C1,C2,C3…..Cn。面額 Ci 的郵票最多可以取 Mi 張。請問,用這些郵票,可以貼出多少面額不同的郵資 (包括 0)。貼郵票時,郵票不必全部使用。

說明


多重揹包,不過比較簡單可以不用比較標準的寫法,直接加判斷。
首先計算出最大能取到的面值,dp[j]表示用前i-1種郵票湊成j時第i種郵票最多能剩多少個,-1表示不能湊成j。如果前i-1種已經能湊出j,那麼dp[j]就等於c[i](代碼中a[i]表示面值,c[i]表示張數);如果用一張郵票i,面值就超出了j,或者用上郵票i都湊不到j,那麼置-1;否則就用掉第i個郵票。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 15;
const int maxm = 1e4;
int a[maxn], c[maxn], dp[maxm], n, m, ans;

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n;
        m = ans = 0;
        for (int i = 0; i < n; ++i)
            scanf("%d", a+i);
        for (int i = 0; i < n; ++i)
        {
            scanf("%d", c+i);
            m += c[i] * a[i];
        }
        memset(dp, -1, sizeof dp);
        dp[0] = 0;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j <= m; ++j)
            {
                if (dp[j] >= 0) dp[j] = c[i];
                else if (j < a[i] || dp[j-a[i]] <= 0)
                    dp[j] = -1;
                else dp[j] = dp[j-a[i]] - 1;
            }
        for (int i = 0; i <= m; ++i)
            if (dp[i] >= 0) ++ans;
        printf("%d\n", ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章