zoukankan      html  css  js  c++  java
  • 【线型DP】洛谷P2066 机器分配

    【线型DP】洛谷P2066 机器分配

    标签(空格分隔): 线型DP


    【题目】

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

    输入格式
    第一行有两个数,第一个数是分公司数N,第二个数是设备台数M。

    接下来是一个N*M的矩阵,表明了第 I个公司分配 J台机器的盈利。

    输出格式
    第1行为最大盈利值

    第2到第n为第i分公司分x台

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

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

    【思路】

    显然是个线型动归,直接蒙上(误)转移方程就行。定义f[i][j]表示第i行共分j台时的最多利润,本题与普通的线型动归的区别在于多了一维循环。
    用脑子想了一想,f[i][j]要是不转移,就是a[i][j](原数据),f[i][j]要是转移,肯定是从这一行这个公司的前面k台的盈利值,上一行即此前算出的f[i-1][j-k]中转移过来,转移方程:f[i][j]=max(f[i-1][k]+a[i][j-k])(0<=k<=j)。
    写的时候是要注意第二层循环总台数时需要倒序,防止重算。
    至于后面的路径,跟普通的线型动归的路径记录差不多,看注释就行了。

    【代码】

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<queue>
    using namespace std;
    const int maxn=50,maxe=25,INF=0x3f3f3f3f;
    int n,m,a[maxe][maxe],f[maxn][maxn],path[maxn][maxe],ans;
    inline int read(){
    	int s=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){
    		if(ch=='-')w=-1;ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    	return s*w;
    }
    int main(){
    	freopen("a.in","r",stdin);
    	n=read(),m=read();
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=m;j++){
    			a[i][j]=read();
    		}
    	}//普通的读入
    	for(int i=1;i<=n;i++){
    		for(int j=m;j>=1;j--){//这里我测了一下,正序倒序都可以,因为某一行某个状态的值不影响同一行其他状态的值
    			f[i][j]=a[i][j];//初始化
    			for(int k=0;k<=j;k++){//因为题目没要求没个公司必选一件,所以要从0开始
    				if(f[i][j]<f[i-1][k]+a[i][j-k]){//转移,严格的小于,不能等于
    					f[i][j]=f[i-1][k]+a[i][j-k];//上一行(这一行以上)选取k台,那这行肯定选取j-k台
    					path[i][j]=k;//记录路径,path[i][j]现在处于第i行,一共选取了j台,这一行选取的台数
    				}
    			}
    		}
    	}
    	cout<<f[n][m]<<endl;
    	int now=n,k=m;
    	int way[maxn];
    	while(1){
    		if(now==1){
    			way[1]=k-path[now][k];
    			break;
    		}
    		way[now]=k-path[now][k];
    		k=path[now][k];
    		now--;
    	}
    	for(int i=1;i<=n;i++){
    		cout<<i<<" "<<way[i]<<endl;
    	}
    }
    

    【难点】

    本道题其实难点在于字典序的处理,集中表现在三个问题
    1.第三层循环是否倒序
    2.k代表前一行还是这一行的台数
    3.path[i][j]里面存k还是j-k

    1/2:

    因为答案要求我们如果有相同答案,输出字典序最小的,这就要求我们第三层循环中正序循环上一行的状态,这里k代表的上一行的断点,这样最先存的路径一定是上一行字典序小的,当然路径还是应该存上一行的(要是存本行的断点第一行的数据不对,可能是没有存)。
    因此,这里还可以这样写:

    	for(int i=1;i<=n;i++){
    		for(int j=m;j>=1;j--){
    			f[i][j]=a[i][j];
    			for(int k=j;k>=0;k--){
    				if(f[i][j]<f[i-1][j-k]+a[i][k]){//这一行的字典序大了,上一行的才小
    					f[i][j]=f[i-1][j-k]+a[i][k];
    					path[i][j]=j-k;
    				}
    			}
    		}
    	}
    	cout<<f[n][m]<<endl;
    	int now=n,k=m;
    	int way[maxn];
    	while(1){
    		if(now==1){
    			way[1]=k-path[now][k];
    			break;
    		}
    		way[now]=k-path[now][k];
    		k=path[now][k];
    		now--;
    	}
    

    但是但是但是但是但是,题目的数据很水,这个数据不管上面的那三个问题都能过
    2 15
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
    于是改了一下子数据:
    3 15
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
    1 1 1 4 1 1 1 1 1 1 1 1 1 1 3
    正确输出:
    6
    1 1
    2 10
    3 4
    这样的话第三层的循环就必须与k的含义以及是否取等对应了(即使你A了的话)


    第二次写题解耗费这么长时间,溜了溜了.jpg

  • 相关阅读:
    [EULAR文摘] 超声腱鞘炎对RA早期诊断的价值
    [EULAR文摘] 脊柱放射学持续进展是否显著影响关节功能
    第24周SDAI缓解能否预测远期RA骨破坏受抑制
    比较多普勒超声与临床缓解标准对RA放射学进展的预测效能
    血药谷浓度能否区分经TNF拮抗剂诱导获得缓解和低活动度的RA患者
    MRI病变能否预测已获临床缓解的早期RA未来放射学进展
    生物制剂时代的SpA研究正站在十字路口_Appel,Sieper2009
    【分享】操作配置文件帮助类
    MongoDB学习笔记-创建、更新、删除文档
    缓存学习笔记-1
  • 原文地址:https://www.cnblogs.com/614685877--aakennes/p/12766494.html
Copyright © 2011-2022 走看看