2016.4.9 NOI codevs动态规划专练

2023-05-24,,

1.NOI 最大子矩阵

1:最大子矩阵

总时间限制: 
1000ms

内存限制: 
65536kB

描述
已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。

比如,如下4 * 4的矩阵

0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2

的最大子矩阵是

9 2
-4 1
-1 8

这个子矩阵的大小是15。
输入
输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。已知矩阵中整数的范围都在[-127, 127]。
输出
输出最大子矩阵的大小。
样例输入

4
0 -2 -7 0 9 2 -6 2
-4 1 -4 1 -1 8 0 -2

样例输出

15

来源
翻译自 Greater New York 2001 的试题
一般做法:N^3时间复杂度

/*方法:枚举每一行所有的区间情况,把这个区间从第一列向下加,加的过程中注意:因为有负数,所以如果之前加的和<0了,我们要舍弃之前的所有结果,重新开始新的矩阵,而且要注意对于ans的更新是在加的过程更新,因为随时有可能加上负数,虽然sum>0,所以加一次都要更新*/
#include<iostream>
using namespace std;
#include<cstdio>
const int N=;
const int INF=<<;
int n,a[N][N];
int main()
{
scanf("%d",&n);
int x;
for(int i=;i<=n;++i)
for(int j=;j<=n;++j)
{
scanf("%d",&x);
a[i][j]=a[i][j-]+x;
}
int ans=-INF;
for(int i=;i<=n;++i)
for(int j=i;j<=n;++j)
{
int tmp=;
for(int k=;k<=n;++k)
{
int num=a[k][j]-a[k][i-];
if(tmp>) tmp+=num;
else tmp=num;
if(tmp>ans) ans=tmp;
}
}
printf("%d\n",ans);
return ;
}

2.NOI 6047:分蛋糕

6047:分蛋糕

总时间限制: 
1000ms

内存限制: 
65536kB

描述

有一块矩形大蛋糕,长和宽分别是整数w 、h。现要将其切成m块小蛋糕,每个小蛋糕都必须是矩形、且长和宽均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的m块小蛋糕中,最大的那块蛋糕的面积下限。

假设w= 4, h= 4, m= 4,则下面的切法可使得其中最大蛋糕块的面积最小。

假设w= 4, h= 4, m= 3,则下面的切法会使得其中最大蛋糕块的面积最小:

输入
共有多行,每行表示一个测试案例。每行是三个用空格分开的整数w, h, m ,其中1 ≤ w, h, m ≤ 20 , m ≤ wh. 当 w = h = m = 0 时不需要处理,表示输入结束。
输出
每个测试案例的结果占一行,输出一个整数,表示最大蛋糕块的面积下限。
样例输入

4 4 4
4 4 3
0 0 0

样例输出
4
6
代码:

/*这个题和之前做的那个棋盘分割很像,但是又有些不同,棋盘分割要求确定每一个矩形。
而在这个题目中,只要矩形的长宽还有切割的次数确定了,无论这个面积的矩形位于大矩形的什么地方,结果都是相同的,那就没有必要固定矩形位置,仅仅固定矩形的大小就可以了
第一次做,我用的就是确定矩形的位置,开了一个五维数组,结果全部超时,虽然样例过了。
f[i][j][k]表示的是i*j这个矩形分割为k份的最大面积的下限。
*/
#include<iostream>
using namespace std;
#include<cstdio>
#define N 21
int f[N][N][N];
int n,m,k;
#include<cstring>
void DP()
{
/*第一次的超时做法*/
/*for(int kk=1;kk<=k;++kk)
for(int x1=0;x1<=n;++x1)
for(int y1=0;y1<=m;++y1)
for(int x2=x1+1;x2<=n;++x2)
for(int y2=y1+1;y2<=m;++y2)
{
if(f[x1][y1][x2][y2][kk]<5000)
continue;
if(kk==1)
{
f[x1][y1][x2][y2][kk]=(x2-x1)*(y2-y1);
continue;
}
for(int x=x1+1;x<x2;++x)
{
f[x1][y1][x2][y2][kk]=min(max(f[x1][y1][x][y2][1],f[x][y1][x2][y2][kk-1]),f[x1][y1][x2][y2][kk]);
f[x1][y1][x2][y2][kk]=min(max(f[x1][y1][x][y2][kk-1],f[x][y1][x2][y2][1]),f[x1][y1][x2][y2][kk]);
}
for(int y=y1+1;y<y2;++y)
{
f[x1][y1][x2][y2][kk]=min(max(f[x1][y1][x2][y][1],f[x1][y][x2][y2][kk-1]),f[x1][y1][x2][y2][kk]);
f[x1][y1][x2][y2][kk]=min(max(f[x1][y1][x2][y][kk-1],f[x1][y][x2][y2][1]),f[x1][y1][x2][y2][kk]);
}
}*/ for(int kk=;kk<=k;++kk)
for(int x1=;x1<=n;++x1)
for(int y1=;y1<=m;++y1)
{
if(f[x1][y1][kk]<)/*记忆化搜索因为输入有多组数据,而且只要i,j,k确定,结果就不变,所以没有必要重复计算*/
continue;
if(kk==)
{
f[x1][y1][kk]=x1*y1;/*DP的初始化*/
continue;
}
for(int x=;x<x1;++x)
{
/* f[x1][y1][kk]=min(max(f[x][y1][kk-1],(x1-x)*y1),f[x1][y1][kk]);*/
/*f[x1][y1][kk]=min(max(x*y1,f[x1-x][y1][kk-1]),f[x1][y1][kk]);*/
for(int p=;p<kk;p++)
f[x1][y1][kk]=min(f[x1][y1][kk],max(f[x][y1][p],f[x1-x][y1][kk-p]));
/*这里与棋盘分割的不同,棋盘分割是上面的方程,因为棋盘分割要求“将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,”,所以必须是一块k-1份,一块1份才可以,
而这个“分蛋糕”只要求蛋糕是矩形,没要求每切一份,剩下的也是矩形*/
}
for(int y=;y<y1;++y)
{
/*f[x1][y1][kk]=min(max(f[x1][y][kk-1],x1*(y1-y)),f[x1][y1][kk]);*/
/* f[x1][y1][kk]=min(max(x1*y,f[x1][y1-y][kk-1]),f[x1][y1][kk]);*/
for(int p=;p<kk;p++)
f[x1][y1][kk]=min(f[x1][y1][kk],max(f[x1][y][p],f[x1][y1-y][kk-p]));
}
} }
int main()
{
memset(f,,sizeof(f));
while(scanf("%d%d%d",&n,&m,&k)==&&n&&m&&k)
{
DP();
printf("%d\n",f[n][m][k]);
}
return ;
}

3. NOI  6049:买书

6049:买书

总时间限制: 
1000ms

内存限制: 
65536kB

描述

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。

问小明有多少种买书方案?(每种书可购买多本)

输入
一个整数 n,代表总共钱数。(0 <= n <= 1000)
输出
一个整数,代表选择方案种数
样例输入

样例输入1:
20 样例输入2:
15 样例输入3:
0

样例输出

样例输出1:
2 样例输出2:
0 样例输出3:
0

代码:

/*变形版的“线段覆盖”,把餐馆向后扩展k长度,作为一个线段就可以了*/
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#define N 101
int t,n,k,f[N];
struct Poi{
int l,r,val;
// bool operator <(const Poi &a)
// const { return r<a.r;}
};
Poi poi[N];
#include<algorithm>
void input()
{
memset(f,,sizeof(f));
memset(poi,,sizeof(poi));
scanf("%d%d",&n,&k);
for(int i=;i<=n;++i)
{
scanf("%d",&poi[i].l);
poi[i].r=poi[i].l+k;
}
for(int i=;i<=n;++i)
scanf("%d",&poi[i].val);
}
int ans=;
void DP()
{ // sort(poi+1,poi+n+1);
for(int i=;i<=n;++i)
f[i]=poi[i].val;
for(int i=;i<=n;++i)
{
for(int j=;j<i;++j)
{
if(poi[i].l>poi[j].r)
f[i]=max(f[i],f[j]+poi[i].val);
} }
}
int main()
{
scanf("%d",&t);
while(t--)
{
input();
DP();
ans=;
for(int i=;i<=n;++i)
ans=max(ans,f[i]);/*一开始犯了一个非常傻逼的错误,在DP中寻找f[i]的最大值,结果漏了f[1]恰好输入的数据有的就是仅仅建立第一个餐馆的利润最大,结果WA了*/
printf("%d\n",ans);
}
return ;
}

4. NOI 4978:宠物小精灵之收服

4978:宠物小精灵之收服

总时间限制: 
1000ms

内存限制: 
65536kB

描述

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。

一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。小智也想收服其中的一些小精灵。然而,野生的小精灵并不那么容易被收服。对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。当小智的精灵球用完时,狩猎也宣告结束。

我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。

小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。

现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。请问,小智该如何选择收服哪些小精灵以达到他的目标呢?

输入
输入数据的第一行包含三个整数:N(0 < N < 1000),M(0 < M < 500),K(0 < K < 100),分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。
之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。
输出
输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。
样例输入

样例输入1:
10 100 5
7 10
2 40
2 50
1 20
4 20 样例输入2:
10 100 5
8 110
12 10
20 10
5 200
1 110

样例输出

样例输出1:
3 30 样例输出2:
0 100

提示
对于样例输入1:小智选择:(7,10) (2,40) (1,20) 这样小智一共收服了3个小精灵,皮卡丘受到了70点伤害,剩余100-70=30点体力。所以输出3 30
对于样例输入2:小智一个小精灵都没法收服,皮卡丘也不会收到任何伤害,所以输出0 100
代码:

/*一个普通的二维01背包,注意体力不能是0,就可以了,
f[j][k]表示在使用了j个球,收到k点伤害的最大捕捉数目。
ans最好DP中更新,因为DP结束以后,最优值可能存在很多位置,伤害和球的数目不一定恰好用完
*/
#include<iostream>
using namespace std;
#include<cstdio>
#define N 1001
#define M 501
int n,m,k;
struct Jl{
int qiu,dam;
};
Jl jl[];
struct Ans{
int sum,tl;
};
Ans ans;
int f[N][M];
void input()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=;i<=k;++i)
{
scanf("%d%d",&jl[i].qiu,&jl[i].dam);
}
}
void DP()
{
ans.sum=;
ans.tl=m;
for(int i=;i<=k;++i)
for(int j=n;j>=jl[i].qiu;--j)
for(int k=m-;k>=jl[i].dam;--k)
{
f[j][k]=max(f[j][k],f[j-jl[i].qiu][k-jl[i].dam]+);
if((f[j][k]>ans.sum)||(f[j][k]==ans.sum&&(m-k)>ans.tl))
{
ans.sum=f[j][k];
ans.tl=m-k;
}
}
}
int main()
{
input();
DP();
printf("%d %d\n",ans.sum,ans.tl);
return ;
}

2016.4.9 NOI codevs动态规划专练的相关教程结束。

《2016.4.9 NOI codevs动态规划专练.doc》

下载本文的Word格式文档,以方便收藏与打印。