3.29总结

最大子矩阵和 PKU 1050

题目简介
描述:给定 N (1 <= N <= 100) 和一个 N * N
的矩阵,求该矩阵的一个子矩阵,使得子矩阵中所有数字的和最大。求出这个最大值。
输入:一个整数 N,然后是 N * N 个绝对值不超过 128
的整数
输出:只有一个值,最大子矩阵和 样例输入: 4 0 -2 -7 0 9 2 -6 2
-4 1 -4 1 -1 8 0 -2
样例输出: 15

问题分析
一般的做法依然是枚举:枚举子矩阵最上和最下的行以及最左和最右的列,但仅是这样就已经是 O(N^4) 的复杂度了。对于 N <= 100 以及 1s 的时限来说,这样的做法是不能够接受的。
如何根据子矩阵的特性来解决此题?
正确解法:每次枚举子矩阵最上的行 u 和最下的行 d,再把这个子矩阵每一列的值相加,压缩成一个一维的数组,对这个数组求其最大子段和,这样就相当于把所有最上的行为 u 、最下的行为 d 的最大子矩阵和求出来了。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 101
using namespace std;
int fun(int b[N], int n)
{
    int i, max, c;
    c = 0;
    max = 0;
    for(i=1; i<=n; i++)
    {
        if(c > 0)
            c = c+b[i];
        else
            c = b[i];
        if(max < c)
            max = c;
    }
    return max;
}
int main()
{
    int i, j, n, max, sum, k;
    int a[N][N], b[N];

    scanf("%d", &n);
    for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
            scanf("%d", &a[i][j]);
    max = 0;
    for(i=1; i<=n; i++)
    {
        for(j=1; j<=n; j++)
            b[j] = 0;
        for(j=i; j <=n; j++)
        {
            for(k=1; k<=n; k++)
                b[k]+=a[j][k];
            sum = fun(b, n);
            if(max < sum)
                max = sum;
        }
    }
    printf("%d\n", max);
    return 0;
}

花束摆放问题

问题描述:
现在有F束不同品种的花束,同时有至少同样数量的花瓶被按顺序摆成一行,其位置固定于架子上,并从1至V按从左到右顺序编号,V是花瓶的数目(F≤V)。花束可以移动,并且每束花用1至F的整数唯一标识。标识花束的整数决定了花束在花瓶中排列的顺序,如果i<j,花束i必须放在花束j左边的花瓶中。
每个花瓶只能放一束花。如果花瓶的数目大于花束的数目,则多余的花瓶空置。 每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以一美学值(一个整数)来表示,空置花瓶的美学值为零。为取得最佳美学效果,必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。请求出具有最大美学值的一种摆放方式。

解体思路:
设A(i,j)表示第i种花束摆在第j个花瓶中获得的美学值。 S[i,k]表示第i种花束摆在第k个之前(包括第k个)的任意某个花瓶中,前i种花束能够获得的最大美学值(之和)。这样,原问题的最优值即为S[F,V]。 其递归方程为:
S[i,k]=max{S[i-1,k-1]+A(i,k),S[i,k-1]},(i>1,k>i); 初始条件为:
S[1,1]=A[1,1]; S[1,k]=max{A(1,k),S[1,k-1]},(k>1);
S[i,i]=S[i-1,i-1]+A(i,i), (i>1)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int dp[105][105];
int a[105][105];
int max(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	int n,m;
	cin>>n>>m;
	int i,j;
	for(i=1;i<=n;i++)
		for (j=1;j<=m;j++)
			cin>>a[i][j];
		for(i=1;i<=n;i++)
			for (j=i;j<=m;j++)
			{
				dp[i][j]=dp[i-1][j-1]+a[i][j];
				if(j>i)
					dp[i][j]=max(dp[i][j],dp[i][j-1]);
			}
			cout<<dp[n][m]<<endl;
			return 0;
}

复制书稿

假设有M本书(编号为1,2,…M),想将每本复制一份,M本书的页数可能不同(分别是P1,P2,…PM)。
任务:将这M本书分给K个抄写员(K<=M) 每本书只能分配给一个抄写员进行抄写,而每个抄写员所分配到的书必须是连续顺序的。
复制工作是同时开始进行的,并且每个抄写员复制一页书的速度都是一样的。所以,复制完所有书稿所需时间取决于分配得到最多工作的那个抄写员的复制时间。
试找一个最优分配方案,使分配给每一个抄写员的页数的最大值尽可能小。

解题思路:设dp[i][j]表示前i本书由j个人复制所需要的最少时间,有状态转移方程
dp[i][j]=min(dp[i][j],max(dp[v][j-1],sum[v+1][i]))
其中1<=i<=m,1<=j<=k,j-1<=v<=i-1,
sum[v+1][j]表示第v+1本书到第i本书的页数之和。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN=510;
int sum[MAXN],path[MAXN],dp[MAXN][MAXN];
int main()
{
    int m,k,i,j,v,T,p,t;
    cin>>T;
    while(T--)
    {
        cin>>m>>k;
        for(sum[0]=0,i=1;i<=m;i++)
        {
            cin>>p;
            sum[i]=sum[i-1]+p;
        }
        memset(dp,-1,sizeof(dp));
        for(dp[0][0]=0,i=1;i<=m;i++)
            for(j=1;j<=i&&j<=k;j++)
            {
                if(j==1)dp[i][j]=sum[i];
                else
                    for(v=j-1;v<=i-1;v++)
                {
                    t=max(dp[v][j-1],sum[i]-sum[v]);
                    if(dp[i][j]==-1||t<=dp[i][j])
                        dp[i][j]=t;
                }
            }
        return 0;
    }
    return 0;
}

最大 m 子段和问题 HDU 1024

题目简介
描述:给定 N (1 <= N <= 1000000) 个绝对值不超过 32768 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出 M 个连续不相交子段,使这 M 个子段的和最大。如果子段全都是负数则最大子段和也为负数。
输入:有一个正整数 M 和 一个正整数 N,后面紧跟 N 个绝对值不大于 32768 的整数
输出:最大 M 子段和
样例输入:
2 6 -1 4 -2 3 -2 3
样例输出:
8

问题分析
定义dp[i][j]为前面j个数字中选取i段的最大和(并且最后一段的结尾是第j个数字).
对于第j数字我们有两种决策,一是加入到最后一段, 二是独立成新段.
状态转移方程
dp[i][j]=max{dp[i][j-1],dp[i-1][k]}+num[j] ;(i-1<=k<=j-1)
注意 dp[i][j]中要保证j>=i,不然没有意义.
ans=max{dp[m][k]} ( m<=k<=n);

对于这种算法我们可以知道 从枚举 i: 0…m ; j: 0…n ; k: i-1…j-1; 时间复杂度接近于 O(mn^2),空间复杂度为O(mn);
对于每个dp[i][j]的递推的时候,我们求了一遍dp[i][j]= max{dp[i-1][k],dp[i][j-1]} ( i-1<=k<= j-1 )
而在递推dp[i][j+1]的时候我们只需要比较dp[i][j]和dp[i-1][j+1],而不需要再循环求一次dp[i][j+1]=max{dp[i-1][k],dp[i][j]}( i-1<=k<= j )
可以用降为dp[j]前面j个数字当前的最大子序列和即是dp[j]=max{dp[i][k]} ( i<=k<=j )
这个时候我们就可以把时间复杂度降到O(m*n);

#include <iostream>
#include <cstdio>
#include <cstring>
int dp[1000005],num[1000005],n,m;
int max(int a,int b)
{
    return a>b ? a:b;
}
int solve()
{
    for(int i=1; i<=m; i++)
    {
        int step=0;
        for(int k=1; k<=i; k++)
            step+=num[k];
        dp[n]=step;
        for(int j=i+1; j<=n; j++)
        {
            step=max(step,dp[j-1])+num[j];
            dp[j-1]=dp[n];
            dp[n]=max(step,dp[n]);
//如果为最多m段的最大子序列和,那么我们可以建立一个max变量,在这里记录每个阶段dp[n],找到最大的dp[n];
        }
    }
    return dp[n];
}
int main()
{
	while(scanf("%d%d",&m,&n)!=EOF)
	{
		for(int i=1;i<=n;i++)
		{
			dp[i]=0;
			scanf("%d",&num[i]);
		}
		printf("%d\n",solve());
	}
	return 0;
}

感想:dp这部分的题对我来说真的是难点,做过的题也不一定能很快的找出状态转移方程,寒假里就在这里掉队了,学起来也有些吃力。这周做题也比较少,上课讲的东西也没有完全消化,学习效率非常的低。。下周改善改善改善!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值