寒假每日一题(四)


前言

补寒假题!


提示:以下是本篇文章正文内容,下面案例可供参考

一、滑雪场设计(有技巧的枚举)

农夫约翰的农场上有 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;
}


总结

我太懒了,希望可以早点补完题,这一次的大部分都是动归题,刚好是我不太会的,也希望多思考一下怎么来的!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 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 现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。 【输入文件】 输入文件martian.in包括三行,第一行有一个正整数N,表示火星人手指的数目(1 <= N <= 10000)。第二行是一个正整数M,表示要加上去的小整数(1 <= M <= 100)。下一行是1到N这N个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。 【输出文件】 输出文件martian.out只有一行,这一行含有N个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。 【样例输入】 5 3 1 2 3 4 5 【样例输出】 1 2 4 5 3 用VC环境写的C语言程序,有详尽注释,一看即懂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值