入门者笔记·DP
作者:互联网
DP最优化
例题:P2066 机器分配
题目描述
总公司拥有高效设备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台机器的最大利润。对于第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<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
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;
}
例题:P2701 [USACO5.3]巨大的牛棚Big Barn
题目描述
农夫约翰想要在他的正方形农场上建造一座正方形大牛棚。他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方。我们假定,他的农场划分成 N x N 的方格。输入数据中包括有树的方格的列表。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚。牛棚的边必须和水平轴或者垂直轴平行。
EXAMPLE
考虑下面的方格,它表示农夫约翰的农场,‘.'表示没有树的方格,‘#'表示有树的方格
1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . # . . . # . .
3 . . . . . . . .
4 . . . . . . . .
5 . . . . . . . .
6 . . # . . . . .
7 . . . . . . . .
8 . . . . . . . .
最大的牛棚是 5 x 5 的,可以建造在方格右下角的两个位置其中一个。
输入输出格式
输入格式:
Line 1: 两个整数: N (1 <= N <= 1000),农场的大小,和 T (1 <= T <= 10,000)有树的方格的数量
Lines 2…T+1: 两个整数(1 <= 整数 <= N), 有树格子的横纵坐标
输出格式:
只由一行组成,约翰的牛棚的最大边长。
输入输出样例
输入样例#1:
8 3
2 2
2 6
6 3
输出样例#1:
5
代码:
f(i, j)表示以(i, j)为右下角的最大正方形的边长。
只有a[i][j]不是树时,(i, j)才能作为正方形的右下角。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<map>
#include<vector>
using namespace std;
int n,m;
int a[1005][1005],f[1005][1005];
int main() {
scanf("%d%d",&n,&m);
memset(a,1,sizeof(a));
int x,y;
for(int i=1; i<=m; i++)
{
scanf("%d%d",&x,&y);
a[x][y]=0;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(a[i][j])
f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1;
int ans=0;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
ans=max(ans,f[i][j]);
printf("%d\n",ans);
return 0;
}
例题:P1103 书本整理
题目描述
Frank是一个非常喜爱整洁的人。他有一大堆书和一个书架,想要把书放在书架上。书架可以放下所有的书,所以Frank首先将书按高度顺序排列在书架上。但是Frank发现,由于很多书的宽度不同,所以书看起来还是非常不整齐。于是他决定从中拿掉k本书,使得书架可以看起来整齐一点。
书架的不整齐度是这样定义的:每两本书宽度的差的绝对值的和。例如有4本书:
21×2
35×3
42×4
13×1
那么Frank将其排列整齐后是:
21×2
42×4
13×1
35×3
不整齐度就是2+3+2=7
已知每本书的高度都不一样,请你求出去掉k本书后的最小的不整齐度。
输入输出格式
输入格式:
第一行两个数字n和k,代表书有几本,从中去掉几本。(1≤n≤100,1≤k<n)
下面的n行,每行两个数字表示一本书的高度和宽度,均小于200。
保证高度不重复
输出格式:
一行一个整数,表示书架的最小不整齐度。
输入输出样例
输入样例#1:
4 1
1 2
2 4
3 1
5 3
输出样例#1:
3
代码:
f[i][j]代表取到第i本书时,已取j本书时,可得的最小值
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
using namespace std;
int n, k, m, Min = 0x7fffffff;
int f[501][501];
struct info
{
int h, w;
}a[1001];
bool cmp(const info & x, const info & y)//排序
{
return x.h < y.h;
}
int main()
{
cin >> n >> k;
m = n - k;
for(int i = 1; i <= n; i++)
scanf("%d %d", &a[i].h, &a[i].w);
sort(a+1, a+n+1, cmp);
memset(f, 20, sizeof(f));
for(int i = 1; i <= n; i++)
f[i][1] = 0;
for(int i = 2; i <= n; i++)
for(int j = 1; j <= i-1; j++)
for(int l = 2; l <= min(i, m); l++)//寻找局部最优解
f[i][l] = min(f[i][l], f[j][l-1] + abs(a[i].w - a[j].w));
//思考:为什么局部最优解可以最终获得全区最优解?
for(int i = m; i <= n; i++)//寻找最优解
Min = min(Min, f[i][m]);
printf("%d\n", Min);
return 0;
}
例题:P1043 数字游戏
题目描述
丁丁最近沉迷于一个数字游戏之中。这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。游戏是这样的,在你面前有一圈整数(一共n个),你要按顺序将其分为m个部分,各部分内的数字相加,相加所得的m个结果对10取模后再相乘,最终得到一个数k。游戏的要求是使你所得的k最大或者最小。
例如,对于下面这圈数字(n=4,m=2):
要求最小值时,((2−1)mod10)×((4+3)mod10)=1×7=7,要求最大值时,为1((2+4+3)mod10)×(−1mod10)=9×9=81。特别值得注意的是,无论是负数还是正数,对10取模的结果均为非负值。
丁丁请你编写程序帮他赢得这个游戏。
输入输出格式
输入格式:
输入文件第一行有两个整数,n(1≤n≤50)和m(1≤m≤9)。以下n行每行有个整数,其绝对值≤10^4,按顺序给出圈中的数字,首尾相接。
输出格式:
输出文件有2行,各包含1个非负整数。第1行是你程序得到的最小值,第2行是最大值。
输入输出样例
输入样例#1:
4 2
4
3
-1
2
输出样例#1:
7
81
代码:
总结一下这种类似DP题目的思路和技巧。
1、破环成链。没有太多的技巧性,具体而言就是把数据存储两遍,使得环形的数据可以链式展开,便于我们去DP。注意该题对圆环的处理。
但最后一定要记得扫一遍答案,取F[i][i+N-1],i:1->N中的最大/小值。
2、前缀和。这个东西并不是在所有情况下都适用,但使用起来真的很方便,可以把O(n)的复杂度优化为O(1)。不过只适用于需要把数据直接相加的地方,比如说这道题。
3、初始化。这里实际上包括两点,一方面是在某些特殊情况下需要初始化,初始化为某特定值(比如本题只分成1段的时候)。另一方面也就是数组初始化,求最大值的时候根本不用管(因为初始默认为0),在求最小值的时候把数组全部赋初值为极大值就好啦。
4、状态表达。一般来说可以用F[i][j]表示在区间[i,j]中怎么怎么样,但由于本题还加了一个分为几段的状态,就把数组直接加一维就好了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<map>
#include<vector>
using namespace std;
int S[110][110][12],B[110][110][12],n,m,qq[110],aa[110];
int mod(int x)
{
return (x%10+10)%10;
}
int main()
{
int i,j,k,t,ma=-1e9,mi=1e9;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) scanf("%d",&qq[i]);
for(i=1;i<=n;i++) qq[n+i]=qq[i];
for(i=1;i<=2*n;i++)
{
aa[i]=qq[i]+aa[i-1];//前缀和
}
for(i=1;i<=2*n;i++)//初始化
{
for(j=1;j<=2*n;j++)
{
for(k=1;k<=m;k++)
S[i][j][k]=1e9;
}
}
for(i=1;i<=n;i++)//初始化
{
for(j=i;j<=i+n-1;j++)
{
S[i][j][1]=B[i][j][1]=mod(aa[j]-aa[i-1]);
}
}
for(i=2;i<=m;i++)//i代表已经分为几块
{
for(j=1;j<=n;j++)//要分割区域的左界
{
for(k=j+i-1;k<=i+j+n-m-1;k++)//右界
{
for(t=j+i-2;t<=k-1;t++)//该区域内的分割点
{
S[j][k][i]=min(S[j][k][i],S[j][t][i-1]*mod(aa[k]-aa[t]));
B[j][k][i]=max(B[j][k][i],B[j][t][i-1]*mod(aa[k]-aa[t]));
}
}
}
}
//主要需要注意for循环嵌套的顺序,由于j,k的范围需要由i确定,所以i置于最外层
for(i=1;i<=n;i++)//重新扫一遍(应对圆环)
{
ma=max(ma,B[i][i+n-1][m]);
mi=min(mi,S[i][i+n-1][m]);
}
printf("%d\n%d\n",mi,ma);
return 0;
}
标签:10,int,输入输出,样例,笔记,入门者,格式,include,DP 来源: https://blog.csdn.net/weixin_43918350/article/details/89245858