题目描述

总公司拥有高效设备 M 台,准备分给下属的 N 个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这 M 台设备才能使国家得到的盈利最大?求出最大盈利值。其中 M≤15,N≤10。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M。

输入

第一行有两个数,第一个数是分公司数 N,第二个数是设备台数 M。一个 N*M 的矩阵,表明了第 I 个公司分配 J 台机器的盈利。

输出

第一行为最大盈利值,接下来 n 行,每行第一个数为公司编号,第二个数为公司分配的机器数。

若题目存在多解,编号越大的公司应优先分得越多的机器。

样例数据
输入 #1 
3 3
30 40 50
20 30 50
20 25 30
输出 #1 
70
1 1
2 1
3 1

1.动态规划解法

一道经典的区间DP练习题。

设f[i][j]为前i个公司总共分配j台机器的最大利润。对于第i家子公司,我们可以给其分配的机器台数为:

0,1,2……m

所以在该区间内枚举一个值k,状态转移方程即为:

f[i][j]=max(f[i-1][j-k],f[i][j]);

那么,如何处理方案输出问题呢?

我们设path[i][j][h]对于前i个公司共分配j台机器的最优方案,第h个公司应分配多少台机器,当状态发生转移时,更新path数组即可。最终的答案就存放在path[n][m][i]之中。

贴代码:

#include<bits/stdc++.h>
using namespace std;
int f[11][16],graph[11][16],path[11][16][11],n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            cin>>graph[i][j];
    }
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)    
            for(int k=0;k<=j;k++)
            {
                if (f[i][j]<f[i-1][j-k]+graph[i][k])
                {
                    f[i][j]=f[i-1][j-k]+graph[i][k];
                    for(int h=1;h<i;h++) path[i][j][h]=path[i-1][j-k][h];//path数组只有在状态发生转移时才更新
                    path[i][j][i]=k;
                }    
            }
    cout<<f[n][m]<<endl;
    for(int i=1;i<=n;i++) cout<<i<<" "<<path[n][m][i]<<endl;
    return 0;
}

如果你依照上述思想写出了dp程序并提交,恭喜你,只有90分。

#那么这是为什么呢?

回到题面,我们会发现小小的一行字,人畜无害的样子:

P.S.要求答案的字典序最小

你会发现如果这样做,方案输出是错的。

如何使字典序最小呢?这需要我们倒着枚举。

设表示的意思为“不给”第i家公司k台机器(k的值域同上),那么状态转移方程需改为:

f[i][j]=max(f[i][k],f[i-1][k]+graph[i][j-k]);

再根据这个,对于path数组的更新操作进行一些微调,即可得到满分程序了:

#include<bits/stdc++.h>
using namespace std;
int f[11][16],graph[11][16],path[11][16][11],n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            cin>>graph[i][j];
    }
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)    
            for(int k=0;k<=j;k++)
            {
                if (f[i][j]<f[i-1][k]+graph[i][j-k])
                {
                    f[i][j]=f[i-1][k]+graph[i][j-k];
                    for(int h=1;h<i;h++) path[i][j][h]=path[i-1][k][h];
                    path[i][j][i]=j-k;//因为改为了“不给”第i家公司k台机器,所以必须如此调整
                }
            }
    cout<<f[n][m]<<endl;
    for(int i=1;i<=n;i++) cout<<i<<" "<<path[n][m][i]<<endl;
    return 0;
}

搜索出奇迹

DFS保证找到最大答案的时候就是字典序最少的,因为我从1号-n号枚举用的多少机器,用的机器数量也是由少到多。当最后得到答案相等的情况下就不用需要比较字典序了,直接return,只有碰到大小不一的时候才更新答案机器数

#include<bits/stdc++.h>
using namespace std;
int n,m,a[20][20],pau[20],f[20],ans;//f[i]是答案机器数,pau是当前假设的机器数量
void dfs(int Nnum,int Nans,int Nm) {//Nnum是现在的公司编号,Nans是现在的盈利,Nm是剩余的机器 
	if(Nm<0) return;
	if(Nnum==n+1) {
		if(Nans>ans) {
			ans=Nans;
			for(int i=1;i<=n;i++) f[i]=pau[i];
		}
		return;
	}
	for(int i=0; i<=m; i++) pau[Nnum]=i,dfs(Nnum+1,Nans+a[Nnum][i],Nm-i);//i枚举这个公司用多少台机器 
	return;
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			scanf("%d",&a[i][j]);
	dfs(1,0,m);
	printf("%d\n",ans);
	for(int i=1; i<=n; i++) printf("%d %d\n",i,f[i]);
	return 0;
}

更多推荐