EOJ 1051 完全加括号的矩阵连乘积
n 个矩阵的矩阵链 A1,A2,…,An A 1 , A 2 , … , A n ,矩阵 Ai A i 的规模为 pi−1∗pi(1≤i≤n) 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
的最少乘法次数,不难得到状态转移方程
由此可以写出递归形式:
#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;
}