DP进阶

EOJ 1051 完全加括号的矩阵连乘积


n 个矩阵的矩阵链 A1,A2,,An A 1 , A 2 , … , A n ,矩阵 Ai A i 的规模为 pi1pi(1in) p i − 1 ∗ p i ( 1 ≤ i ≤ n ) Ai A i Ai+1 A i + 1 是相容可乘的,求最优的完全加括号方案,使得依此次序计算矩阵链乘积: ni=1Ai ∏ i = 1 n A i 所需要的乘法次数最少。

说明


显然是动态规划问题,用dp[i][j]表示从 Ai A i Aj A j 的最少乘法次数,不难得到状态转移方程

dp[i][j]={0min{dp[i][k]+dp[k+1][j]+p[i1]p[k]p[j]}i=ji<=k<j d p [ i ] [ j ] = { 0 i=j m i n { d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + p [ i − 1 ] 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值