前言
补寒假题!
提示:以下是本篇文章正文内容,下面案例可供参考
一、滑雪场设计(有技巧的枚举)
农夫约翰的农场上有 N 个山峰,每座山的高度都是整数。
在冬天,约翰经常在这些山上举办滑雪训练营。
不幸的是,从明年开始,国家将实行一个关于滑雪场的新税法。
如果滑雪场的最高峰与最低峰的高度差大于17,国家就要收税。
为了避免纳税,约翰决定对这些山峰的高度进行修整。
已知,增加或减少一座山峰 x 单位的高度,需要花费 x2 的金钱。
约翰只愿意改变整数单位的高度,且每座山峰只能修改一次。
请问,约翰最少需要花费多少钱,才能够使得最高峰与最低峰的高度差不大于17。
输入格式
第一行包含整数 N。
接下来 N 行,每行包含一个整数,表示一座山的高度。
输出格式
输出一个整数,表示最少花费的金钱。
数据范围
1≤N≤1000,
数据保证,每座山的初始高度都在 0∼100 之间。
输入样例:
5
20
4
1
24
21
输出样例:
18
样例解释
最佳方案为,将高度为 1 的山峰,增加 3 个单位高度,将高度为 24 的山峰,减少 3 个单位高度。
由于次数最多84次,所以可以用枚举,这里举例说明了是整数,所以,可以用Σ(h[i]-x[i])来写,如果是小数就不可以了,就类似三分吧1420围栏问题。
就是在枚举所有可能的区间,在范围之万就移到左右边界位置处
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string.h>
#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
const int N=1010;
int n;
int h[n];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>h[i];
int res=1e8;
for(int i=0;i+17<=100;i++)
{
int cost=0,l=i,r=i+17;
for(int j=0;j<n;j++)
if(h[j]<l)
cost+=(l-h[j])*(l-h[j]);
else if(h[j]>r)cost+=(h[j]-r)*h[j]-r);
res=min(res,cost);
}
cout<<res<<endl;
}
二、整数集合划分
给定一个包含 N 个正整数的集合,请你将它划分为两个集合 A1 和 A2,其中 A1 包含 n1 个元素,A2 包含 n2 个元素。
集合中可以包含相同元素。
用 S1 表示集合 A1 内所有元素之和,S2 表示集合 A2 内所有元素之和。
请你妥善划分,使得 |n1−n2| 尽可能小,并在此基础上 |S1−S2| 尽可能大。
输入格式
第一行包含整数 N。
第二行包含 N 个正整数。
输出格式
在一行中输出 |n1−n2| 和 |S1−S2|,两数之间空格隔开。
数据范围
2≤N≤105,
保证集合中各元素以及所有元素之和小于 231。
输入样例1:
10
23 8 10 99 46 2333 46 1 666 555
输出样例1:
0 3611
输入样例2:
13
110 79 218 69 3721 100 29 135 2 6 13 5188 85
输出样例2:
1 9359
难度:简单
来源:PAT甲级真题1113
要分n为奇数还是偶数,|n1-n2|如果是偶数就是0,如果是奇数,就是1
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
int n;
int w[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&w[i]);
int s1=0,s2=0;
sort(w,w+n);
for(int i=0;i<n/2;i++)
s1+=w[i];
for(int i=n/2;i<n;i++)
s2+=w[i];
printf("%d %d",n%2,s2-s1);
return 0;
}
三、合唱队形(dp 最长上升子序列)
N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2…,K,他们的身高分别为 T1,T2,…,TK, 则他们的身高满足 T1<…Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N,表示同学的总数。
第二行有 N 个整数,用空格分隔,第 i 个整数 Ti 是第 i 位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2≤N≤100,
130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
最高的那位左边和右边是完全独立的,就可以分治,左边取到最大值,右边取到最大值,这样子左右两边分别取最大,也就是求最长上升子序列
求最长上升子序列有两个方法,使用dp来做,O(n^2),也可以用贪心来做,O(nlogn)
运用动归的做法!
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
//dp好写一些,此为dp做法
int n;
int h[N];
int f[N],g[N];//f是以i结尾的最长上升子序列,g是以i结尾的最长下降子序列
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
if(h[j]<h[i])
f[i]=max(f[i],f[j]+1);
}
for(int i=n;i;i--)
{
g[i]=1;
for(int j=n;j>i;j--)
if(h[j]<h[i])
g[i]=max(g[i],g[j]+1);
}
int res=0;
for(int k=1;k<=n;k++)
res=max(res,f[k]+g[k]-1);
cout<<n-res<<endl; //最多要剔除n-res个小朋友
return 0;
}
四、火星人(思维)
人类终于登上了火星的土地并且见到了神秘的火星人。
人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。
这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。
火星人用一种非常简单的方式来表示数字——掰手指。
火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 1,2,3……。
火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。
一个火星人用一个人类的手演示了如何用手指计数。
如果把五根手指——拇指、食指、中指、无名指和小指分别编号为 1,2,3,4 和 5,当它们按正常顺序排列时,形成了 5 位数 12345,当你交换无名指和小指的位置时,会形成 5 位数 12354,当你把五个手指的顺序完全颠倒时,会形成 54321,在所有能够形成的 120 个 5 位数中,12345 最小,它表示 1;12354 第二小,它表示 2;54321 最大,它表示 120。
下表展示了只有 3 根手指时能够形成的 6 个 3 位数和它们代表的数字:
三位数 123,132,213,231,312,321
代表的数字 1,2,3,4,5,6
现在你有幸成为了第一个和火星人交流的地球人。
一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。
你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。
输入数据保证这个结果不会超出火星人手指能表示的范围。
输入格式
输入包括三行,第一行有一个正整数 N,表示火星人手指的数目。
第二行是一个正整数 M,表示要加上去的小整数。
下一行是 1 到 N 这 N 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。
输出格式
输出只有一行,这一行含有 N 个整数,表示改变后的火星人手指的排列顺序。
每两个相邻的数中间用一个空格分开,不能有多余的空格。
数据范围
1≤N≤10000,
1≤M≤100
输入样例:
5
3
1 2 3 4 5
输出样例:
1 2 4 5 3
第一个做法:这一题也可以直接用一个库函数来做,next_permutation这样一个函数,你个他一串数字,他就可以给你返回下一个排列,虽然不会考这样一个函数,但是要清楚啊,要知道有这么一个函数
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010;
int n,m;
int w[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i];
while(m--)
{
next_permutation(w+1,w+n+1);
}
for(int i=1;i<=n;i++)
cout<<w[i]<<" ";
cout<<endl;
return 0;
}
第二个做法:(要弄懂他的原理,在原理上进行优化,还有面试官也有可能让你写出它的源代码),尽量变后面,不要动前面,如果后面已经是降序,就已经不可能让他更大
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010;
int n,m;
int w[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i];
while(m--)//O(n)
{//就是将permutation这个函数的实现再敲了一遍
int k=n;
while(w[k-1]>w[k])k--;//找到第一个不是升序的地方
int t=k;
while(w[t+1]>w[k-1])t++;//找到第一个比他大的最小的的元素,换位置
swap(w[k-1],w[k]);
reverse(w+k,w+n+1);//为了让这个数先最小,所以就使用reverse,然后降序排列
}
for(int i=1;i<=n;i++)
cout<<w[i]<<" ";
cout<<endl;
return 0;
}
五、摘花生(dp)
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。
1.gif
输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围
1≤T≤100,
1≤R,C≤100,
0≤M≤1000
输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16
给一个二维矩阵,从左上角走到右下角
dp分析法
①状态表示(1.集合 —从(1,1)走到(i,j)的所有路线集合 2.属性—最大值) ②状态计算
他嘞就是要算f(i,j)就是要算(1,1)到(i-1,j)的最大值,也就是f(i-1,j)+w(i,j)和f(i,j-1)+w(i,j)他们两个其中的最大值。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int w[N][N],f[N][N];
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>w[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
f[i][j]=max(f[i-1][j],f[i][j-1]+w[i][j]);
cout<<f[n][m]<<endl;
}
return 0;
}
六、最大的和(dp)
给定一个包含整数的二维矩阵,子矩形是位于整个阵列内的任何大小为 1×1 或更大的连续子阵列。
矩形的总和是该矩形中所有元素的总和。
在这个问题中,具有最大和的子矩形被称为最大子矩形。
例如,下列数组:
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,表示方形二维数组的大小。
从第二行开始,输入由空格和换行符隔开的 N2 个整数,它们即为二维数组中的 N2 个元素,输入顺序从二维数组的第一行开始向下逐行输入,同一行数据从左向右逐个输入。
数组中的数字会保持在 [−127,127] 的范围内。
输出格式
输出一个整数,代表最大子矩形的总和。
数据范围
1≤N≤100
输入样例:
4
0 -2 -7 0 9 2 -6 2
-4 1 -4 1 -1
8 0 -2
输出样例:
15
这是一个二维题,先看一个一维的吧!
连续子数组的最大和
输入一个 非空 整型数组,数组里的数可能为正,也可能为负。
数组中一个或连续的多个整数组成一个子数组。
求所有子数组的和的最大值。
要求时间复杂度为 O(n)。
样例
输入:[1, -2, 3, 10, -4, 7, 2, -5]
输出:18
我们只用求所有的f[i],f[i]就是所有以i结尾的区间,这是一个递推的过程,所有以i结尾的可以分为两大类,后面是不变的,所有f[i]结尾的最大值,所以f[i]=max(wi,f(i-1)+wi)就可以了。(即f[i]=max(0,f(i-1)))+wi
二维版本就是枚举上下边界,由于数量比较小,所以O(n^3)是可以实现的。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int s[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
s[i][j]=s[i-1][j]+x;//求他们的前缀和
}
int res=-300;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)//枚举一个上下界
{
int f=0;
for(int k=1;k<=n;k++)
{
int w=s[j][k]-s[i-1][k];//第k列所有元素的和
f=max(f,0)+w;
res=max(res,f);
}
}
cout<<res<<endl;
return 0;
}
总结
我太懒了,希望可以早点补完题,这一次的大部分都是动归题,刚好是我不太会的,也希望多思考一下怎么来的!