dp题目总结(2)————资源分配类

本文总结了几道关于资源分配的动态规划题目,包括机器分配、苹果树问题、马棚问题、时间安排、复制书稿和花店橱窗等。通过对每个问题的描述、输入输出格式和状态转移方程的解析,展示了如何使用动态规划解决这类问题。

题目1

机器分配

题目描述

某总公司拥有高效生产设备M台,准备分给下属的N个分公司。各分公司若获得这些设备,可以为总公司提供一定的盈利。问:如何分配这M台设备才能使公司得到的盈利最大?求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数M。其中M<=100,N<=100。

输入格式

第一行为两个整数M,N。接下来是一个N×M的矩阵,其中矩阵的第i行的第j列的数Aij表明第i个公司分配j台机器的盈利。所有数据之间用一个空格分隔。

输出格式

只有一个数据,为总公司分配这M台设备所获得的最大盈利。

样例数据

input

3 2
1 2 3
2 3 4

output

4

状态:
dp[i][j]——前i 个公司分j 台设备的最大盈利。

状态转移方程:
如果前i − 1 个公司已经分配完了,考虑第i 个公司分配的机器数量,设分得k 台,

k 可能为i ~j ,那么前i − 1 个公司只能分配j − k台。
设a [ x ] [ y ] 表示第x个公司分配y台设备的盈利。
因此,只需要考虑第i ii个公司分配了多少台机器:

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

边界:
dp[i][0]=0,i 个公司分配0 台设备盈利为0 
dp[0][j]=0,0 个公司分配j 台设备盈利为0 

输出方案
最后的答案为dp[n][m],即n 个公司分m 台设备的最优解。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int m,n;
int a[110][110],f[110][110];
int main()
{
	freopen("allot.in","r",stdin);
	freopen("allot.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
		{
			cin>>a[i][j];	
		}	
		f[i][0]=0;
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<=n;j++)
		{
			f[i][j]=f[i-1][0];
			for(int k=0;k<=j;k++)
			{
				if(f[i][j]<f[i-1][j-k]+a[i][k])
				{
					f[i][j]=f[i-1][j-k]+a[i][k];
				}
			}
		}
	}
	cout<<f[m][n];
	return 0;
}

 补充:

此类问题有的时候爆空间,可以用滚动数组来写;

本题可以根据相邻两层对2取余结果不同来完成数组的滚动;

#include<bits/stdc++.h>
using namespace std;
int m,n;
int a[110][110];
int f[3][110];
void init()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
		}
	}
}
void c_dp()
{
	for(int i=1;i<=m;i++)
	{
		f[1][i]=a[1][i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int pp=0;
			for(int k=0;k<=j;k++)
			{
				pp=max(f[(i-1)%2][k]+a[i][j-k],pp);
			}
			f[i%2][j]=pp;
		}
	}
	cout<<f[n%2][m];
}
int main()
{
	freopen("allot.in","r",stdin);
	freopen("allot.out","w",stdout);
	init();
	c_dp();
}

题目2

两棵苹果树

题目描述

很少有人知道奶牛爱吃苹果。农夫约翰的农场上有两棵苹果树(编号为1和2),每一棵树上都长满了苹果。奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果从树上落下。但是,由于苹果掉到地上会摔烂贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果)。贝茜吃东西很快,所以她接到苹果后仅用几秒钟就能吃完。

每一分钟,两棵苹果树其中的一棵会掉落一个苹果。贝茜已经过了足够的训练,只要站在树下就一定能接住这棵树上掉落的苹果。同时,贝茜能够在两棵树之间快速移动(移动时间远少于1分钟),因此当苹果掉落时她必定站在两棵树其中的一棵下面。此外,奶牛不愿意不停地往返于两棵树之间,因此会错过一些苹果。

苹果每分钟掉落一个,共T(1<=T<=1000)分钟,贝茜最多愿意移动W(1<=W<=100)次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。

开始时贝茜在1号树下。

输入格式

第1行:由空格隔开的两个整数:T和W。

第2..T+1行:1或2(每分钟掉落苹果的树的编号)。

输出格式

一行:在贝茜移动次数不超过W的前提下她能接到的最多苹果数

样例数据

input

7 2
2
1
1
2
2
1
1

output

6

用d[i][j]表示前i分钟移动j次接的苹果数

状态转移方程:

      j为偶数:  dp[i][j]=max(dp[i-1][j]+flag[i][2],dp[i-1][j-1]+flag[i][2]);

      j为奇数:  dp[i][j]=max(dp[i-1][j]+flag[i][1],dp[i-1][j-1]+flag[i][1]);

#include<bits/stdc++.h>
using namespace std;
int flag[1100][3];
int dp[1010][110];
int m,n;
int main()
{
	freopen("apple.in","r",stdin);
	freopen("apple.out","w",stdout); 
	cin>>m>>n;
	for(int i=1;i<=m;i++)
	{
		int x;
		cin>>x;
		flag[i][x]=1;
	}
	for(int i=1;i<=m;i++)
	{
		dp[i][0]=dp[i-1][0]+flag[i][1];
	}
	for(int i=1;i<=m;i++)
	{
		 for(int j=1;j<=n;j++)
	    {
	        if(j&1)
			    dp[i][j]=max(dp[i-1][j]+flag[i][2],dp[i-1][j-1]+flag[i][2]);
			else
			    dp[i][j]=max(dp[i-1][j]+flag[i][1],dp[i-1][j-1]+flag[i][1]);
	    }
	}
	int ans=-1;    
	for(int i=0;i<=n;i++)
	{
		 ans=max(ans,dp[m][i]);
	}
    cout<<ans<<endl;
	return 0;	
}

题目3

马棚 

题目描述

每天,小明和他的马外出,然后他们一边跑一边玩耍。当他们结束的时候,必须带所有的马返回马棚,小明有K个马棚。

他把他的马排成一排然后跟随它走向马棚,因为他们非常疲劳,小明不想让他的马做过多的移动。

因此他想了一个办法:将马按照顺序放在马棚中,后面的马放的马棚的序号不会小于前面的马放的马棚的序号。

而且,他不想他的K个马棚中任何一个空置,也不想任何一匹马在外面。已知共有黑、白两种马,而且它们相处得并不十分融洽。

如果有i个白马和j个黑马在一个马棚中,那么这个马棚的不愉快系数将是i∗j。所有k个马棚不愉快系数的和就是系数总和。

确定一种方法把n匹马放入k个马棚,使得系数总和最小。

输入格式

在第一行有两个数字:n(1≤n≤500)和k(1≤k≤n)。

在接下来的n行是n个数。在这些行中的第i行代表队列中的第i匹马的颜色:1意味着马是黑色的,0意味着马是白色的。

输出格式

只输出一个单一的数字,代表系数总和可能达到的最小值。

样例数据

input

6 3 
1    
1
0   
1
0
1

{6匹马,3个马棚}

{第1匹马为黑马}

{第3匹马为白马}

output

2

 

对于前i个马棚,装前j匹马的不愉快系数,

可以考虑对于前i-1个马棚,装前k匹马,

然后剩下的一个马棚装l+1到j匹马。

枚举分界点k,在所有可能的情况中取得一个最小值即可。

dp[i][j]=min(dp[i][j],dp[i−1][l]+f[l+1][j])

记得预处理一下黑马白马的不愉快系数;

#include<bits/stdc++.h>
using namespace std;
int dp[555][555],f[555][555];
int a[555],b[555],w[555];
int n,k;
int main()
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		b[i]=b[i-1];
		w[i]=w[i-1];
		if(a[i]==1)
		{
			b[i]++;
		 } 
		else w[i]++;
	}
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			f[i][j]=(b[j]-b[i])*(w[j]-w[i]);
		}
	}
	memset(dp,10,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		dp[1][i]=b[i]*w[i];
	} 
	for(int i=1;i<=k;i++)
	{
		for(int j=i;j<=n-k+i;j++)
		{
			for(int l=i-1;l<j;l++)
			{
				dp[i][j]=min(dp[i][j],dp[i-1][l]+f[l][j]);
			}
		}
	}
	cout<<dp[k][n];
	return 0;
}

题目4

时间安排

题目描述

小王参加的考试是几门科目的试卷放在一起考,一共给t分钟来做。他现在已经知道每门科目花的时间和得到的分数的关系,还有写名字要的时间(他写自己的名字很慢)请帮他算一下他最高能得几分。

总分一定时,第一门科目成绩尽量高,第一门科目成绩也一样时,第二门科目成绩尽量高…………以次类推。如果放弃某一门的考试(花的时间为0),那么名字也就不用写了。

输入格式

第一行三个正整数t,n,name。 t是总时间,n表示考n个科目,name表示写名字要的时间(每一门科目写名字时间一样)。

接下来n行,每行t个正整数,第i个数表示时间为i时这门科目的分数(不一定递增)。时间为0时这门科目的分数为0,所以就不读入了。

输出格式

一个数,即总分。

样例数据

input

5 3 1
1 2 3 4 5
2 3 3 4 6
3 3 3 2 5

output

6

f[i][j]数组表示前i门花j分钟可得到的最高分

转移方程

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

此题与工程分配类似,多了一个name花费时间,

因此在比较的时候还要减掉写名字花的时间:

即a[i][k-name]

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,t,name;
int a[110][110],f[110][110];
int main()
{
	freopen("score.in","r",stdin);
	freopen("score.out","w",stdout);
	cin>>t>>n>>name;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=t;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=t;j++)
		{
			f[i][j]=f[i-1][j];
			for(int k=name;k<=j;k++)
			{
				f[i][j]=max(f[i][j],f[i-1][j-k]+a[i][k-name]);
			}
		}
	}
	cout<<f[n][t];
	return 0;
}

题目5

复制书稿

题目描述

现在要把m本有顺序的书分给k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。

现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

输入格式

第一行两个整数m,k;(k≤m≤500)

第二行m个整数,第i个整数表示第i本书的页数

输出格式

共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。

k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

样例数据

input

9 3
1 2 3 4 5 6 7 8 9

output

1 5
6 7
8 9

最短时间为抄写页数最多的时间,所以要先求最多的抄写页数,从中选择最小值

1. 设状态:f[i][j]//将前i本书分给j个人抄写需要的最短时间;

2. 初始状态:f[i][1];//一个人抄写前i本书需要的最短时间就是前i本书的页数

 最终状态:f[m][k];//将前m本书分给j个人抄写需要的最短时

3. 状态转移方程:先分配最后一个人抄写的页数,求得最大值 。

max(f[i-l][j-1],d[i]-d[i-l]);
   最后求最短时间

f[i][j]=min(max(f[i-l][j-1],d[i]-d[i-l]))}

求具体方案:
在用动态规划求得最优值后,然后用贪心的思想,将最后一本书按逆序将书分配给k个人抄写,从第k个人开始,如果他还能写,就给他,直到分配完毕。

#include<bits/stdc++.h>
using namespace std;
int i,j,x,y,m,n,k,t,l;
int a[501];//存储每本书的页数
int f[501][501];//f[i][j]表示前i本书分给j个人抄写的最短复制时间
int d[501];//d[j]表示前j本书的总页数
int print(int i,int j)
{
	int t,x;
	if(j==0) return 0;
	if(j==1) 
	{
		cout<<1<<" "<<i<<endl;
		return 0;
	}
	t=i;
	x=a[i];
	while(x+a[t-1]<=f[m][k])
	{
		x=x+a[t-1];
		t--;
	}
	print(t-1,j-1);
	cout<<t<<" "<<i<<endl;
}
int main()
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
	cin>>m>>k;
	for(i=0;i<=500;i++)
	{
		for(j=0;j<=500;j++)
		{
			f[i][j]=10000000;//对f[i][j]进行初始化 
		}
	}	
	for(i=1;i<=m;i++)
	{
		cin>>a[i];
		d[i]=d[i-1]+a[i];
		f[i][1]=d[i];//把前i本书都分给1个人抄写需要的最短时间 
	}
	for(j=2;j<=k;j++)//j个人  
	{
		for(i=1;i<=m;i++)//i本书 
		{
			for(l=1;l<=i-1;l++)//最后一个人抄写的页数
			{
				if(max(f[i-l][j-1],d[i]-d[i-l])<f[i][j])
					f[i][j]=max(f[i-l][j-1],d[i]-d[i-l]);
			} 
		}
	}
	print(m,k);
} 

题目6

花店橱窗

题目描述

假设你想以最美观的方式布置花店的橱窗。现在你有F束不同品种的花束,同时你也有至少同样数量的花瓶被按顺序摆成一行。这些花瓶的位置固定于架子上,并从1至V顺序编号,V是花瓶的数目,从左至右排列,则最左边的是花瓶1,最右边的是花瓶V。花束可以移动,并且每束花用1至F间的整数唯一标识。标识花束的整数决定了花束在花瓶中的顺序,如果<I<J,则令花束I必须放在花束J左边的花瓶中。

例如,假设一束杜鹃花的标识数为1,一束秋海棠的标识数为2,一束康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。如果花瓶的数目大于花束的数目。则多余的花瓶必须空置,且每个花瓶中只能放一束花。

每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以美学值(一个整数)来表示,空置花瓶的美学值为零。

在上述例子中,花瓶与花束的不同搭配所具有的美学值,如下表所示

花瓶编号12345
杜鹃花723-5-2416
秋海棠521-41023
康乃馨-215-4-2020

例如,根据上表,杜鹃花放在花瓶2中,会显得非常好看;但若放在花瓶4中则显得十分难看。

为取得最佳美学效果,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。如果有不止一种的摆放方式具有最大的美学值,你只要输出字典序最小的那种摆放方式。

输入格式

第一行包含两个数:F,V。

随后的F行中,每行包含V个整数,Aij 即为输入文件中第(i+1)行中的第j个数。

1≤F≤100,其中F为花束的数量,花束编号从1至F。

F≤V≤100,其中V是花瓶的数量。

−50≤Aij≤50,其中Aij是花束i在花瓶j中的美学值。

输出格式

一行是程序所产生摆放方式的美学值。

第二行必须用f个数表示摆放方式,即该行的第k个数表示花束k所在的花瓶的编号。

样例数据

input

3 5 
7  23  -5 -24  16
5  21  -4  10  23
-21 5  -4 -20  20

output

53
2  4  5

 用a[i][j]表示美学值,b[i][j]表示前i束花放入j个花瓶中的最优解,

那么通过动态规划依次求出1-i束花在1-j个花瓶中的最优解,

最后输出m[f][v]即

首先对于第一束花,最优值是和美学值相等的,

所以我们直接赋值a[1][j]=b[1][j]。

在仔细审题发现题目有一个天然的限制:第j朵花最多只能放在第j个瓶子中,

于是我们就可以枚举它放在可以放到的每一个瓶子(从i到j)里所产生的情况下

产生的子问题从而计算在前i个瓶子中放j朵花的最大每美学值;

即m[i-1][k]为第i-1束花在第k个花瓶里的最优解。

将k从i到j-1进行循环,所以状态转移方程:

即b[i][j]=max(b[i-1][k]+a[i][j],b[i][j]);

这道题还要求输出过程,只需要开一个ans数组记录在dp[i][j]时的选择就行了,

到了最后在用栈结构反推过来输出;(确实可以,这真牛)
 

#include<bits/stdc++.h>
using namespace std;
int f,v,dp[101][101],ma[101][101],ans[101][101];
int main()
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
    cin>>f>>v;
    for(int i=1;i<=f;i++)
    {
        for(int j=1;j<=v;j++)
            cin>>ma[i][j];
    }
    for(int i=1;i<=f;i++)
        dp[i][i]=dp[i-1][i-1]+ma[i][i];
    for(int i=1;i<=v;i++)
        for(int j=1;j<=f;j++)
        {
            dp[i][j]=-1e+8;//初值,因为有负数所以0不行
            for(int k=0;k<=i-j;k++)//枚举子问题
            {
                if(dp[k+j-1][j-1]+ma[j][k+j]>dp[i][j])
                {
                    ans[i][j]=k+j;
                    dp[i][j]=dp[k+j-1][j-1]+ma[j][k+j];
                }
            }
        }
    cout<<dp[v][f]<<endl;//输出最大值
    stack<int> st;
    int a=f,b=v;
    for(int i=1;i<=f;i++)//反推过程
    {
        st.push(ans[b][a]);
        b=ans[b][a]-1;
        a--;
    }
    bool f=1;
    while(!st.empty())//输出过程
    {
        if(f)
        {
            f=0;
            cout<<st.top();
        }
        else cout<<" "<<st.top();
        st.pop();
    }
}

当然也可以不用栈,我是比较懵的,同学大佬的写法,

#include<bits/stdc++.h>
using namespace std;
int F,v;
int a[110][110];
int f[110][110];
int g[110][110];
int ans[110];
void init()
{
	cin>>F>>v;
	for(int i=1;i<=F;i++)
		for(int j=1;j<=v;j++) scanf("%d",&a[i][j]);
}
void work()
{
	for(int i=0;i<=F+5;i++)
		for(int j=0;j<=v+5;j++) f[i][j]=-1e9;
	for(int i=1;i<=v-F+1;i++) f[1][i]=a[1][i];
	for(int i=2;i<=F;i++)
		for(int j=i;j<=v-(F-i);j++)
			for(int k=i-1;k<j;k++)
				if(f[i-1][k]+a[i][j]>f[i][j])
				{
					f[i][j]=f[i-1][k]+a[i][j];
					g[i][j]=k;
				}
}
void print()
{
	int j;
	int ans1=-1e9;
	for(int i=F;i<=v;i++)
	{
		if(f[F][i]>ans1)
		{
			ans1=f[F][i];
			j=i;
		}
	}
	cout<<ans1<<endl;
	ans[F]=j;
	for(int i=F-1;i>=1;i--)
	{
		ans[i]=g[i+1][j];
		j=g[i+1][j];
	}
	for(int i=1;i<F;i++) cout<<ans[i]<<" ";
	cout<<ans[F]<<endl;
}
int main()
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
	init();
	work();
	print();
	return 0;
}

题目7

雇用计划

【问题描述】

一位管理项目的经理想要确定每个月需要的工人,他当然知道每月所需的最少工人数。当他雇佣或解雇一个工人时,会有一些额外支出。一旦一个工人被雇佣,即使他不工作,他也将得到工资。这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资。现他在考虑一个问题:为了把项目的费用控制在最低,他将每月雇佣或解雇多少个工人。

【输入格式】

输入文件含有三行。第一行为月数n(不超过12)。第二行含雇佣一个工人的费用,一个工人的工资和解雇一个工人的费用(≤100)。第三行含n个数,分别表示每月最少需要的工人数(≤1000)。每个数据之间有一个空格隔开。

【输出格式】

输出仅一行,表示项目的最小总费用。

【样例】

employ.in

3
4 5 6 
10 9 11

employ.out

199

该3个月份中最大工人数目为11,最小工人数目为9

第一个月我们可以雇佣10个工人,或者11个工人

第二个月我们需要的工人底线是9个,如果该月雇佣9个

(需要解雇x人,x由上一个月雇佣人数决定(上一个月人数减去9),

如果该月雇佣10个人(需要解雇人数x,也由上一个月雇佣人数决定),

如果该月雇佣11个人,那么现在我们可能还需要继续的雇佣工人,

第三个月也依次类推,

这样不同的雇佣策略导致我们的花费肯定不同,在这些方案在花费中找到最小的即可

dp的关键在于明白:前面一个月雇佣多少个人使得这个月的花费最小

 dp[i][j] :代表在第i个月的时候,雇佣J个人的总费用(雇佣费+工资)

dp[i][j]=

dp[i-1][k](前面一个月雇佣k个人,可以使得前面的i个月(包括i)的花费最小)+这个月的费用

=dp[i-1][k]+abs(j-k)*v+j*工资  v表示解雇或者雇佣的权值,由j,k相对大小决定(分类)

#include<bits/stdc++.h>
using namespace std;
int n;
int a,b,c;
int aa[20];
int f[15][1010];
int m;
int main()
{
	freopen("employ.in","r",stdin);
	freopen("employ.out","w",stdout);
	cin>>n;
	cin>>a>>b>>c;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&aa[i]);
		m=max(m,aa[i]);
	}
	memset(f,10,sizeof(f));
	for(int i=1;i<=m;i++) 
	{
		f[1][i]=(a+b)*i;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			for(int k=aa[i-1];k<=m;k++)
			{
				if(k<j)
				{
					f[i][j]=min(f[i][j],f[i-1][k]+a*(j-k)+b*j);
				} 
				else
				{
					if(k==j) f[i][j]=min(f[i][j],f[i-1][k]+b*j);
					else f[i][j]=min(f[i][j],f[i-1][k]+c*(k-j)+b*j);
				} 
				
			}
		}
	}
	int ans=1e9;
	for(int i=aa[n];i<=m;i++)
	{
		ans=min(f[n][i],ans);
	}
	cout<<ans<<endl;
	return 0;
}

心累。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值