ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

入门者笔记·DP

2019-06-07 08:48:16  阅读:269  来源: 互联网

标签:10 int 输入输出 样例 笔记 入门者 格式 include 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有