【马蹄集】第十一周——模拟与枚举专题

模拟与枚举专题






MT2012 竹鼠的白色季节

难度:黄金    时间限制:1秒    占用内存:250M
题目描述

小码哥的竹鼠养殖场中的竹鼠都在一条直线的隔间里,一共有 n ( 1 ≤ n ≤ 100000 ) n(1≤n≤100000) n(1n100000) 只竹鼠,它们的坐标分别为 p 1 , p 2 , p 3 , … , p n ( 0 ≤ p i ≤ 1 0 8 ) p_1, p_2,p_3,…,p_n (0≤p_i≤10^8 ) p1,p2,p3,,pn(0pi108)
又到了冬季,竹鼠们也要谈恋爱,因为竹鼠们都太胖了,它们的活动范围有限,这里统一规定它们的活动范围为 d ( 0 ≤ d ≤ 1 0 4 ) d(0≤d≤10^4 ) d(0d104)
由于竹鼠之间也需要双向奔赴,所以如果两只竹鼠之间的距离小于等于 d d d,则称它们是有缘的一对竹鼠,问小码哥的养殖场里一共有多少对有缘的竹鼠?
注:一只竹鼠可能存在于多个有缘的竹鼠对之中,多只竹鼠可能在同一个坐标上。

格式

输入格式:第一行为两个用空格隔开的数字 n n n d d d;;
     第二行为 n n n 个整数,表示 n n n 只竹鼠在直线上的坐标。
输出格式:输出一个整数,表示有缘的竹鼠对 。

样例 1

输入:5 3

   1 2 4 8 9

输出:4


相关知识点: 枚举


题解


题目说了很多,但实际只需要知道一件事即可:“如果两只竹鼠之间的距离小于等于 d d d,则称它们是一对有缘的竹鼠,问小码哥的养殖场里一共有多少对有缘的竹鼠?”。所以,本题实际上就是让我们统计位置差距在一定范围的数对。

具体的求解思路很简单,将所有竹鼠的位置进行排序,然后再扫描排序后的数组并统计满足要求的差距即可,但是有一点需要注意,在统计差距时为了避免重复(因为对于一对有缘的竹鼠而言,两个竹鼠各自在计算差距时都会将彼此纳入计算),我们只进行 “单向” 统计,具体实现如下(已 AC):

/*
    MT2012 竹鼠的白色季节 
*/

#include<bits/stdc++.h> 
using namespace std;

const int MAX = 100005;
int ary[MAX];

int getPredestined(int ary[], int n, int d)
{
    
    
    int predestined = 0;
    
    // 对竹鼠的位置数组进行排序
    sort(ary,ary+n);
    
    // 扫描全部竹鼠以寻找有缘鼠
    // 注:为避免重复统计,规定进行单向寻找(下面的代码执行后向配对)
    for(int i=0;i<n-1;i++) 
        for(int j=i+1;j<n;j++)
            if(ary[j]-ary[i] <= d) predestined++;
            else break;
    
    return predestined;
}

int main( )
{
    
    
    // 录入数据 
    int n, d; cin>>n>>d;
    for(int i=0;i<n;i++) cin>>ary[i];
    
    // 输出有缘的竹鼠对 
    cout<<getPredestined(ary, n, d)<<endl;
     
    return 0;
}


MT2013 照亮街道

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

有一条可以视为线段的街道,长度为 L L L,其中有 n n n 个路灯,每个路灯能照亮的半径都是相同的,求最小的照亮半径 r r r 使得整条街道上的每一个点都能被照亮。

格式

输入格式:第一行两个正整数 n , L n,L nL
     第二行为 n n n 个实数,表示 n n n 个路灯的坐标,数据保证在 0 0 0 L L L 之间。
输出格式:输出一行一个实数表示答案,答案保留两位小数。

样例1

输入:7 15
   15 5 3 7 9 14 0

输出:2.50

备注

其中: n ≤ 1000 , L ≤ 1 0 9 n≤1000,L≤10^9 n1000,L109


相关知识点:枚举


题解


求解此题最简单的办法就是暴力枚举。由于题目让我们保留两个小数,因此求解步骤可以从 0.01 开始进行枚举,直到某个半径长度能够将整条街都照亮时,该半径即为所求。

但是从数学的角度出发,肯定有更好的算法。

对于这条路灯间距不一致的街而言,如果要让所有地方都充满光明,则路灯的照明半径肯定只取决于那段“路灯隔得最远的路段”。比如,在题目给出的数据中,从下图可以很明显地看出,其只取决于路灯 9 和 14 的距离。

在这里插入图片描述

因此,我们的求解目标就从“找最小半径”,转换为“找最远路灯”。最远路灯的求解就很简单了:对路灯的位置数组进行排序,然后遍历该数组求差分的最大值即可。但是!一定要注意两个端点位置(即绝对位置为 0 和 L 处)的与路灯的差距,这需要单独计算!并参与对最大值的比较。

下面给出基于以上思路写出的完整代码(已 AC):

/*
    MT2013 照亮街道 
*/

#include<bits/stdc++.h> 
using namespace std;

const int MAX = 1005;
// 注:用 float 只能通过一个例子,这里必须用 double 
double ary[MAX];

int main( )
{
    
    
    // 录入数据 
    int n, L; cin>>n>>L;
    for(int i=0;i<n;i++) cin>>ary[i];
    
    // 对路灯数组排序 
    sort(ary,ary+n);
    
    // 求差距最大的间隙
    double gap=0;
    for(int i=0;i<n-1;i++)
        gap = max(gap,ary[i+1]-ary[i]);
    
    // 与路段的首尾间隙进行比较
    gap = max(gap/2, max(ary[0],L-ary[n-1]));
    
    // 输出操作次数(注意格式控制) 
    printf("%.2f\n", gap);
     
    return 0;
}


MT2014 打靶

难度:黄金    时间限制:3秒    占用内存:128M
题目描述

小码哥在练习射箭,他要射穿 n n n 个靶子,且每个靶子有高度 h i h_i hi。箭会因为受到靶子的摩擦力而下坠,当射穿一个靶子后箭的高度会下降 1。小码哥可以射任意高度的箭。
求出小码哥最少需射几箭。

格式

输入格式:第一行一个整数 n n n
     第二行 n n n 个整数 h i h_i hi
输出格式:输出一个整数,表示小码哥最少需要射几箭。

样例 1

输入:5
   2 1 5 4 3
输出:2

备注

其中: 1 ≤ n , h i ≤ 1 0 6 1≤n,h_i≤10^6 1n,hi106
小码哥与靶子的占位如下(不会有两个相邻的靶子紧紧贴在一起):
小码哥 ——> 靶子1,靶子2,靶子3,……,靶子n。


相关知识点:模拟枚举


题解


题目的意思很简单:射出的箭具有无限射程,但是每当该箭射穿一个靶子时就会降低1个单位的高度。现在有一系列的靶,问最少要射几支箭才能把这些靶都击穿。

题目给出的测试数据如下:

在这里插入图片描述

显然,两支箭就能将所有靶全部击倒(顺序可任意):
取第一支箭高度为2,它在射穿0号靶子后,会顺势射穿1号靶;
取第二支箭高度为5,它在射穿2号靶子后,会顺势射穿3、4号靶;
这样就能用最少的箭将全部靶子都射穿。

接下来我们来思考这里面的奥义。首先要知道一件事,箭的射程是无限的,对于射出的任意箭,它必定能将一系列高度为等差递减序列的靶子全部射穿。这实际上提示了我们,该题就是让我们其统计高度连续递减靶子的丛数。但要注意一件事,这个等差递减序列的先后关系必须和各靶子的相对位置对应。因此,我们在模拟这个射箭的过程时,要考虑到这一点。所以现在问题的关键就在于,如何去记录高度连续递减靶子的丛数?要知道,对于题目输入的靶子而言,那些高度连续递减靶子位置可能本身并不连续。

这里提供一种简单有效的办法:高度消融。具体做法是,对于输入的高度,我们每次都将当前高度进行下沉,这样最终就能把不同位置的具有连续递减性质的丛数给记录下来。例如,对于高度序列:{5,3,4,3,1},系统第一次运行时,高度数组 height[ ]={0}为空:

  1. 输入高度 5,由于这之前并没有这个高度(即 height[5]==0),说明当前这个高度是第一个,因此需要记 cnt=1,然后将其下沉,即现在高度数组的内容为:height[5-1]=height[4]++,即 height={0,0,0,0,1,0};
  2. 输入高度 3,由于这之前并没有这个高度(即 height[3]==0),说明当前这个高度是第一个,因此需要记 cnt=2,然后将其下沉,即现在高度数组的内容为:height[3-1]=height[2]++,即 height={0,0,1,0,1,0};
  3. 输入高度 4,由于当前高度数组存在 height[4] 非0,说明当前高度存在着从上一次消融下来的数据,也就是说在这之前射出了一支高度为 4+1=5 的箭,那么当前位置将被射穿。因此,要将当前位置进行消融并下沉,于是置 height[4]–,height[4-1]=height[3]++,即 height={0,0,1,1,0,0};
  4. 输入高度 3,由于当前高度数组存在 height[3] 非0,说明当前高度存在着从上一次消融下来的数据,也就是说在这之前射出了一支高度为 3+1=4 的箭,那么当前位置将被射穿。因此,要将当前位置进行消融并下沉,于是置 height[3]–,height[3-1]=height[2]++,即 height={0,0,2,0,0,0};
  5. 输入高度 1,由于这之前并没有这个高度(即 height[1]==0),说明当前这个高度是第一个,因此需要记 cnt=3,然后将其下沉,即现在高度数组的内容为:height[1-1]=height[0]++,即 height={1,0,1,0,0,0}。

算法结束,输出计数cnt即得到了最终需要的最少的箭数。

根据这样的思路可写出求解本题的完整代码(已 AC):

/*
    MT2014 打靶 
*/

#include<bits/stdc++.h> 
using namespace std;

const int MAX = 1000005;
int ary[MAX];

int main( )
{
    
    
    int n, height, cnt=0; cin>>n;
    while(n--){
    
    
        // 获取当前靶子的高度 
        cin>>height;
        
        // 根据当前靶子高度是否存在决定是否消融和计数 
        ary[height]?ary[height]--:cnt++;
        
        // 执行下沉 
        ary[height-1]++;
    }
    cout<<cnt<<endl;     
    return 0;
}


MT2026 二维坐标点移动

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

给定一个 N ∗ M N*M NM 的二维坐标矩阵A,并且给定一个整数K,K表示移动步数,矩阵A中的所有点 A [ i ] [ j ] A[i][j] A[i][j] 的移动操作如下:

  1. A [ i ] [ j ] A[i][j] A[i][j] 移动到 A [ i ] [ j + 1 ] ( j ≤ M − 2 ) A[i][j+1](j ≤ M-2) A[i][j+1]jM2
  2. A [ i ] [ M − 1 ] A[i][M-1] A[i][M1] 移动到 A [ i + 1 ] [ 0 ] ( i ≤ N − 2 ) A[i+1][0](i ≤ N-2) A[i+1][0]iN2
  3. A [ N − 1 ] [ M − 1 ] A[N-1][M-1] A[N1][M1] 移动到 A [ 0 ] [ 0 ] A[0][0] A[0][0]

最后输出移动K次后新的二维坐标矩阵A。

格式

输入格式:第一行输入两个整数 N, M;
     接下来 N 行,每行 M 个数,即 A [ i ] [ j ] ( 0 ≤ i ≤ N , 0 ≤ j ≤ M ) A[i][j](0 ≤ i ≤ N, 0 ≤ j ≤ M) A[i][j]0iN,0jM
     最后一行一个整数 K。
输出格式:N 行,每行 M 个数,即修改之后的 A [ i ] [ j ] A[i][j] A[i][j]

样例 1

输入:3 3
   1 2 3
   30 40 50
   10 20 30
   2

输出:20 30 1
   2 3 30
   40 50 10

备注

其中: 1 ≤ N , M ≤ 50 , − 1000 ≤ A [ i ] [ j ] ≤ 1000 , 0 ≤ K ≤ 1000 1≤N,M≤50,-1000 ≤ A[i][j]≤ 1000,0 ≤ K ≤ 1000 1N,M50,1000A[i][j]1000,0K1000


相关知识点: 模拟


题解


实际上,这道题的要求是“将矩阵中的每个元素往后移动K个单位”。由于处理对象是一个矩阵,因此在将其中的元素向后移动时有3个情况(假设矩阵规格为 N ∗ M N*M NM,索引从0开始):

  1. 待移动元素位于矩阵最右下角的位置(即 A [ N − 1 ] [ M − 1 ] A[N-1][M-1] A[N1][M1]),这种情况,其下一个位置为矩阵的最开始位置(即 A [ 0 ] [ 0 ] A[0][0] A[0][0]);
  2. 待移动元素位于矩阵任意行(除最后一行)的末尾(即 A [ i ] [ M − 1 ] A[i][M-1] A[i][M1]),这种情况,其下一个位置为该元素所在下一行的最开始位置(即 A [ i + 1 ] [ 0 ] A[i+1][0] A[i+1][0]);
  3. 待移动元素位于除1和2以外的任意位置(设为 A [ i ] [ j ] A[i][j] A[i][j]),这种情况,其下一个位置为 A [ i ] [ j + 1 ] A[i][j+1] A[i][j+1]

在这里插入图片描述

从上面的分析不难看出,其变换规则还是很简单的。因此,最简单直接的办法就是设计一个规格和输入数据一致的二维数组,然后遍历原始数据,通过 switch 控制不同类型的数据进行位置变换并存储即可。这样的方式显然是可行的。

但仔细思考上面的三个情况,不难发现其本质都是在描述“将数据向后移动若干位”的含义。因此,为了简化上述复杂的分情况讨论环节,我们可以用一种更简单的方式进行:将原始数据存储至一维空间,执行“将数据向后移动若干位”后再按二维的方式进行输出。

根据这样的思路最终可得到以下代码(已 AC):

/*
    MT2026 二维坐标点移动 
*/
 
#include<bits/stdc++.h> 
using namespace std;

const int MAX = 2505;
int ary[MAX], offset[MAX]; 

int main() 
{
    
     
    // 录入数据 
    int n, m, k; cin>>n>>m;
    int LEN = n*m;
    for(int i=0;i<LEN;i++) cin>>ary[i];
    cin>>k;
    
    // 对数组进行移位处理 
    for(int i=0;i<LEN;i++) offset[(i+k)%LEN] = ary[i];
    
    // 输出数据
    k = 0;
    for(int i=0;i<n;i++){
    
    
        for(int j=0;j<m;j++)
            cout<<offset[k++]<<" ";
        cout<<endl;
    }
    return 0; 
}


MT2027 一秒成零

难度:白银    时间限制:1秒    占用内存:128M
题目描述

给定一个正整数 n ,请写一个函数 Steps,将 n 通过以下操作变成0,并且返回操作次数。

  1. n 为偶数,则 n=n/2;
  2. n 为奇数,则 n=n-1。
格式

输入格式:第一行输入正整数 n n n
输出格式:输出操作次数。

样例1

输入:14

输出:6

备注

其中: 1 ≤ n ≤ 1 0 7 1≤n≤10^7 1n107


相关知识点:模拟


题解


这是一道纯模拟题,照题目意思做就行。要么递推要么递推(取决于鉴于数据范围),下面给出求解的两个算法:

/*
   MT2027 一秒成零
     
*/

#include<bits/stdc++.h> 
using namespace std;

// 递推实现(数的取值范围较大时宜用) 
int Steps(int n){
    
    
    int optCnt = 0;
    while(n){
    
    
        if(n&1) n--;
        else n/=2;
        optCnt++;
    }
    return optCnt;
} 

// 递归实现 
int Steps_(int n){
    
    
    if(n==0) return 0; 
    if(n&1) return Steps(n-1)+1;
    return Steps(n/2)+1; 
} 

int main( )
{
    
    
    // 获取输入 
    int n; cin>>n;
    
    // 输出操作次数
    cout<<Steps(n);
     
    return 0;
}

END


猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/130708064