NOIP普及组历年题目

文章目录

  • 2003
    • 413. 乒乓球
    • 414. 数字游戏
    • 415. 栈
    • 416. 麦森数
  • 2004
    • 417. 不高兴的津津
    • 418. 花生采摘
    • 419. FBI树
    • 420. 火星人
  • 2005
    • 421. 陶陶摘苹果
    • 422. 校门外的树
    • 423. 采药
    • 424. 循环
  • 2006
    • 425. 明明的随机数
    • 426. 开心的金明
    • 427. Jam的计数法
    • 428. 数列
  • 2007
    • 429. 奖学金
    • 430. 纪念品分组
    • 431. 守望者的逃离
    • 432. Hanoi双塔问题
  • 2008
    • 433. ISBN号码
    • 434. 排座椅
    • 435. 传球游戏
    • 436. 立体图
  • 2009
    • 437. 多项式输出
    • 438. 分数线划定
    • 439. 细胞分裂
    • 440. 道路游戏
  • 2010
    • 441. 数字统计
    • 442. 接水问题
    • 443. 导弹拦截
    • 444. 三国游戏
  • 2011
    • 445. 数字反转
    • 446. 统计单词数
    • 447. 瑞士轮
    • 448. 表达式的值
  • 2012
    • 449. 质因数分解
    • 450. 寻宝
    • 451. 摆花
    • 452. 文化之旅
  • 2013
    • 453. 计数问题
    • 454. 表达式求值
    • 455. 小朋友的数字
    • 456. 车站分级
  • 2014
    • 457. 珠心算测验
    • 458. 比例简化
    • 459. 螺旋矩阵
    • 460. 子矩阵
  • 2015
    • 461. 金币
    • 462. 扫雷游戏
    • 463. 求和
    • 464. 推销员
  • 2016
    • 465. 买铅笔
    • 466. 回文日期
    • 467. 海港
    • 468. 魔法阵
  • 2017
    • 469. 成绩
    • 470. 图书管理员
    • 471. 棋盘
    • 472. 跳房子
  • 2018
    • 473. 标题统计
    • 474. 龙虎斗
    • 475. 摆渡车
    • 476. 对称二叉树
  • 2019
    • 1161. 数字游戏
    • 1162. 公交换乘
    • 1163. 纪念品
    • 1164. 加工零件
  • 2020
    • 2767. 优秀的拆分
    • 2768. 直播获奖
    • 2769. 表达式
    • 2770. 方格取数
  • 2021
    • 4086. 分糖果
    • 4087. 插入排序
    • 4088. 网络连接
    • 4089. 小熊的果篮

2003

413. 乒乓球

国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及。

其中 11 分制改革引起了很大的争议,有一部分球员因为无法适应新规则只能选择退役。

华华就是其中一位,他退役之后走上了乒乓球研究工作,意图弄明白 11 分制和 21 分制对选手的不同影响。

在开展他的研究之前,他首先需要对他多年比赛的统计数据进行一些分析,所以需要你的帮忙。

华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在 11 分制和 21 分制下,双方的比赛结果(截至记录末尾)。

比如现在有这么一份记录,(其中 W 表示华华获得一分,L 表示华华对手获得一分):

WWWWWWWWWWWWWWWWWWWWWWLW 

在 11 分制下,此时比赛的结果是华华第一局 11 比 0 获胜,第二局 11 比 0 获胜,正在进行第三局,当前比分 1 比 1。

而在 21 分制下,此时比赛结果是华华第一局 21 比 0 获胜,正在进行第二局,比分 2 比 1。

如果一局比赛刚开始,则此时比分为 0 比 0。

你的程序就是要对于一系列比赛信息的输入(WL 形式),输出正确的结果。

输入格式
每个输入文件包含若干行字符串(每行至多 20 个字母),字符串由大写的 W、L 和 E 组成。

其中 E 表示比赛信息结束,程序应该忽略 E 之后的所有内容。

输出格式
输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。

其中第一部分是 11 分制下的结果,第二部分是 21 分制下的结果,两部分之间由一个空行分隔。

输入样例:
WWWWWWWWWWWWWWWWWWWW
WWLWE
输出样例:
11:0
11:0
1:1

21:0
2:1

字符串模拟

//在11分制下,一局比赛结束的条件是:某一方达到11分,且分差达到2;
//在21分制下,一局比赛结束的条件是:某一方达到21分,且分差达到2;

#include 
#include 

using namespace std;


void work(string str, int score)//每局赛点分数为score 
{
    int a = 0, b = 0;//a:W的分数 
    for (int i = 0; i < str.size() && str[i] != 'E'; i ++ )
    {
        if (str[i] == 'W') a ++ ;//W赢
        else b ++ ;//L赢

        if (max(a, b) >= score && abs(a - b) >= 2)//规则比分>=11且比分相差2球 结束
        {
            printf("%d:%d\n", a, b);
            a = b = 0;//本场比赛归0, 判断下一场比赛, 双方比分归0
        }
    }
    printf("%d:%d\n", a, b);//结束再输出当前场比分【正在进行中的实时比分】
}


int main()
{
    string str, s;//可以不写头文件
    while (cin >> s) str += s; //读入多行字符串s ==> 预处理:多局比赛合成一行str ,遇到'E'停止【E不一定在结尾,str包含多局比赛】

    work(str, 11);
    puts("");
    work(str, 21);

    return 0;
}

414. 数字游戏

丁丁最近沉迷于一个数字游戏之中。

这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。

游戏是这样的,在你面前有一圈整数(一共 n 个),你要按顺序将其分为 m 个部分,各部分内的数字相加,相加所得的 m 个结果对 10 取模后再相乘,最终得到一个数 k。

游戏的要求是使你所得的 k 最大或者最小。

例如,对于下面这圈数字(n=4,m=2):

NOIP普及组历年题目_第1张图片

当要求最小值时,((2−1)mod10)×((4+3)mod10)=1×7=7,要求最大值时,为 ((2+4+3)mod10)×(−1mod10)=9×9=81。

特别值得注意的是,无论是负数还是正数,对 10 取模的结果均为非负值。

丁丁请你编写程序帮他赢得这个游戏。

输入格式
输入文件第一行有两个整数,n 和 m。

以下 n 行每行一个整数,其绝对值不大于 10000,按顺序给出圈中的数字,首尾相接。

输出格式
输出文件有两行,各包含一个非负整数。

第一行是你程序得到的最小值,第二行是最大值。

数据范围
1≤n≤50,
1≤m≤9
输入样例:
4 2
4
3
-1
2
输出样例:
7
81

415. 栈

栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。

栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。

栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。

宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。

宁宁考虑的是这样一个问题:一个操作数序列,从 1,2,一直到 n,栈 A 的深度大于 n。

现在可以进行两种操作,

将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)。
将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)。
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列。

你的程序将对给定的 n,计算并输出由操作数序列 1,2,…,n 经过操作可能得到的输出序列的总数。

输入格式
输入文件只含一个整数 n。

输出格式
输出文件只有一行,即可能输出序列的总数目。

数据范围
1≤n≤18
输入样例:
3
输出样例:
5

数组出入栈有效排序 == 卡特兰数对应项

规律:卡特兰数: c a t ( n ) = C 2 n n ( n + 1 ) cat(n) = \frac{C_{2n}^{n}}{(n + 1)} cat(n)=(n+1)C2nn
在本题中我们使用公式来计算卡特兰数(打表) :时间复杂度 O( n 2 n^2 n2)

组合数数学意义:从n个数中选m个数
选第n个数[最后一个数]:是否选第m个数:【状态转移】
C n m = C n − 1 m − 1 + C n − 1 m − 1 C_{n}^{m} = C_{n - 1}^{m - 1} + C_{ n - 1}^{m - 1} Cnm=Cn1m1+Cn1m1

//想刷哪个知识点找收藏最多的提单:洛谷

//直接分析: 放第一个  放第二个(第一个出/不出)  放第3个(第1、2出/不出)     ... 放最后1个

#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 40;

LL c[N][N];//组合数打表

int main()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ )
            if (!j) c[i][j] = 1;//规定:C(i,0) = 1;
            else c[i][j] = c[i - 1][j] + c[i - 1][j - 1];//组合数公式 c(m,n) = c(m,n - 1) + c(m - 1, n - 1)

    int n;
    cin >> n;

    cout << c[n * 2][n] / (n + 1) << endl;

    return 0;
}

416. 麦森数

形如 2 P − 1 2^P−1 2P1 的素数称为麦森数,这时 P 一定也是个素数。

但反过来不一定,即如果 P 是个素数, 2 P − 1 2^P−1 2P1 不一定也是素数。

到 1998 年底,人们已找到了 37 个麦森数。

最大的一个是 P=3021377,它有 909526 位。

麦森数有许多重要应用,它与完全数密切相关。

任务:从文件中输入 P,计算 2 P − 1 2^P−1 2P1 的位数和最后 500 位数字(用十进制高精度数表示)。

输入格式
文件中只包含一个整数 P。

输出格式
第一行:十进制高精度数 2 P − 1 2^P−1 2P1 的位数。

第 2−11 行:十进制高精度数 2 P − 1 2^P−1 2P1 的最后 500 位数字。(每行输出 50 位,共输出 10 行,不足 500 位时高位补 0)

不必验证 2 P − 1 2^P−1 2P1 与 P 是否为素数。

数据范围
1000 输入样例:
1279
输出样例:
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087

快速幂 + 高精度*高精度 【大数处理】

k位数的范围: 1 0 k − 1 < = x < 1 0 k 10^{k-1} <= x < 10^k 10k1<=x<10k,(k≥0)
我们发现在 1 0 k < = x < 1 0 k + 1 10^{k} <= x < 10^{k + 1} 10k<=x<10k+1,(k≥0) 之间的数均有 k+1 位。因此对于任意正整数 x,它的位数是 ⌊log10^x⌋+1
cmath库中有函数 log10(),直接使用即可。

//https://www.acwing.com/solution/content/4414/
//此题并没有需要判断质数,仅需大数处理算法

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 510;

int a[N], c[N], res[N];

void mul(int a[N], int b[N], int res[N]) //高精度a*高精度b = 结果res
{
    memset(c, 0, N * 4);//c全局,多次循环计算:每次使用初始化
    for (int i = 0; i < 500; i ++ )
        for (int j = 0; j < 500; j ++ )
            if (i + j < 500)
                c[i + j] += a[i] * b[j];//c存a、b数组每位乘积的值
    for (int i = 0, t = 0; i < 500; i ++ )
    {
        t += c[i];//取乘积
        res[i] = t % 10;//取当前位值
        t /= 10;//留下高位进位余数 + 下一次乘积
    }
}

void qmi(int p) // 计算 2^p
{
    res[0] = 1;
    a[0] = 2;
    while (p)
    {
        if (p & 1) mul(a, res, res);
        mul(a, a, a);//高精度 * 高精度 = a = a * a
        p >>= 1;
    }
    res[0] -- ;
}


int main()
{
    int p;
    scanf("%d", &p);
    printf("%d\n", int(log10(2) * p) + 1);//输出位数

    qmi(p);
    for (int i = 499, j = 0; j < 10; j ++ )//输出格式:10行, 每行50位
    {
        for (int k = 0; k < 50; k ++, i -- ) //i-- 逆序存放, 输出逆序
            printf("%d", res[i]);
        puts("");
    }
    return 0;
}



2004

417. 不高兴的津津

津津上初中了。

妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参加妈妈为她报名的各科复习班。

另外每周妈妈还会送她去学习朗诵、舞蹈和钢琴。

但是津津如果一天上课超过八个小时就会不高兴,而且上得越久就会越不高兴。

假设津津不会因为其它事不高兴,并且她的不高兴不会持续到第二天。

请你帮忙检查一下津津下周的日程安排,看看下周她会不会不高兴;如果会的话,哪天最不高兴。

输入格式
输入文件包括七行数据,分别表示周一到周日的日程安排。

每行包括两个小于 10 的非负整数,用空格隔开,分别表示津津在学校上课的时间和妈妈安排她上课的时间。

输出格式
输出文件包括一行,这一行只包含一个数字。

如果不会不高兴则输出 0,如果会则输出最不高兴的是周几(用 1, 2, 3, 4, 5, 6, 7 分别表示周一,周二,周三,周四,周五,周六,周日)。

如果有两天或两天以上不高兴的程度相当,则输出时间最靠前的一天。

输入样例:
5 3
6 2
7 2
5 3
5 4
0 4
0 6
输出样例:
3

pair二元组

/*简单的事情复杂化 ~嘎嘎乐~ */

#include 
#include 
#include 

#define x first
#define y second

using namespace std;

typedef pair<int , int> PII;

PII week[10];

int n, m;
int res = 0, maxv = -1;
int main()
{
    for(int i = 0; i < 7; i++)
    {   
        cin >> n >> m;
        week[i].x = n , week[i].y = m;
    }    
    
    for(int i = 0; i < 7; i++)
    {
        int x = week[i].x, y = week[i].y;
        if(x + y > 8 && maxv < x + y ) maxv = x + y, res = i + 1;//星期几
    }
    
    if(maxv == -1) puts("0");
    else printf("%d\n", res);
    
    return 0;
}

max_element(起始地址, 起始地址 + 遍历长度:个数)


#include 
#include 
#include 

using namespace std;

const int N = 10;

int n, a[N];

int main()
{
    for (int i = 1; i <= 7; i ++ ){
        int x, y;
        cin >> x >> y;
        a[i] = x + y;
    }
    int* maxv = max_element(a + 1, a + 8);
    if (*maxv > 8)
        cout << maxv - a << endl;
    else
        cout << '0' << endl;
    return 0;
}

简单版


#include 
#include 
#include 

using namespace std;

int a, b;

int id, sum = 8;

int main()
{
    for (int i = 1; i <= 7; i ++ ){
        cin >> a >> b;
        if (a + b > sum)
            sum = a + b, id = i;
    }
    cout << id << endl;
    return 0;
}

418. 花生采摘

鲁宾逊先生有一只宠物猴,名叫多多。这天,他们两个正沿着乡间小路散步,突然发现路边的告示牌上贴着一张小小的纸条:“欢迎免费品尝我种的花生! ——熊字”。

鲁宾逊先生和多多都很开心,因为花生正是他们的最爱。

在告示牌背后,路边真的有一块花生田,花生植株整齐地排列成矩形网格(如图 1)。

有经验的多多一眼就能看出,每棵花生植株下的花生有多少。

为了训练多多的算术,鲁宾逊先生说:“你先找出花生最多的植株,去采摘它的花生;然后再找 出剩下的植株里花生最多的,去采摘它的花生;依此类推,不过你一定要在我限定的时间内回到路边。”

我们假定多多在每个单位时间内,可以做下列四件事情中的一件:

从路边跳到最靠近路边(即第一行)的某棵花生植株;
从一棵植株跳到前后左右与之相邻的另一棵植株;
采摘一棵植株下的花生;
从最靠近路边(即第一行)的某棵花生植株跳回路边。
现在给定一块花生田的大小和花生的分布,请问在限定时间内,多多最多可以采到多少个花生?

注意 可能只有部分植株下面长有花生,假设这些植株下的花生个数各不相同。

例如在图 2 所示的花生田里,只有位于 (2,5),(3,7),(4,2),(5,4) 的植株下长有花生,个数分别为 13,7,15,9。

沿着图示的路线,多多在 21 个单位时间内,最多可以采到 37 个花生。

NOIP普及组历年题目_第2张图片

输入格式
输入文件的第一行包括三个整数,M,N 和 K,用空格隔开;表示花生田的大小为 M×N,多多采花生的限定时间为 K 个单位时间。

接下来的 M 行,每行包括 N 个非负整数,也用空格隔开;第 i+1 行的第 j 个整数 Pij 表示花生田里植株 (i,j) 下花生的数目,0 表示该植株下没有花生。

输出格式
输出文件包括一行,这一行只包含一个整数,即在限定时间内,多多最多可以采到花生的个数。

数据范围
1≤M,N≤20,
0≤K≤1000,
0≤Pij≤500
输入样例:
6 7 21
0 0 0 0 0 0 0
0 0 0 0 13 0 0
0 0 0 0 0 0 7
0 15 0 0 0 0 0
0 0 0 9 0 0 0
0 0 0 0 0 0 0
输出样例:
37

419. FBI树

我们可以把由 0 和 1 组成的字符串分为三类:全 0 串称为 B 串,全 1 串称为 I 串,既含 0 又含 1 的串则称为 F 串。

FBI 树是一种二叉树,它的结点类型也包括 F 结点,B 结点和 I 结点三种。

由一个长度为 2N 的 01 串 S 可以构造出一棵 FBI 树 T,递归的构造方法如下:

T 的根结点为 R,其类型与串 S 的类型相同;
若串 S 的长度大于 1,将串 S 从中间分开,分为等长的左右子串 S1 和 S2;由左子串 S1 构造 R 的左子树 T1,由右子串 S2 构造 R 的右子树 T2。
现在给定一个长度为 2N 的 01 串,请用上述构造方法构造出一棵 FBI 树,并输出它的后序遍历序列。

输入格式
第一行是一个整数 N。

第二行是一个长度为 2N 的 01 串。

输出格式
包含一行,这一行只包含一个字符串,即 FBI 树的后序遍历序列。

数据范围
0≤N≤10
输入样例:
3
10001011
输出样例:
IBFBBBFIBFIIIFF
NOIP普及组历年题目_第3张图片

#include 
#include 
#include 
#include 

using namespace std;

void dfs(int n, string state)//递归logn次 
{
    if (n > 0)
    {
        dfs(n - 1, state.substr(0, 1 << n - 1));//对半分[0,2^{n - 1}] 与 [2^{n - 1}, 结束]
        dfs(n - 1, state.substr(1 << n - 1));
    }

    int one = 0, zero = 0;//递归到叶子节点的父节点 :判断左右孩子1与0的个数
    for (int i = 0; i < state.size(); i ++ )
        if (state[i] == '0') zero ++ ;
        else one ++ ;
    
    //后序遍历: 先递归左,再递归右 ,递归到子树最后输出【左右根】
    
    if (zero && one) printf("F");//有1有0 : 10 或 01
    else if (zero) printf("B");//只有0 : 00
    else printf("I");//只有1 : 11
}

int main()
{
    int n;
    string state;
    cin >> n >> state;

    dfs(n, state);//从n开始,递归logn次, 每次划分位置为2^{n-1}

    return 0;
}




// #include
// using namespace std;
// int s[10001],n,t;
// string st;         
// void doit(int l,int r){//抽风佬的解:我不理解         后序遍历递归到叶子节点:最后才输出 
//     if(l==r){ 
//         if(st[l-1]=='0')cout<<'B';
//             else cout<<'I';
//         return;
//     }
//     doit(l,(l+r)/2);
//     doit((l+r)/2+1,r);
//     if(s[r]-s[l-1]==0)cout<<'B';
//         else if(s[r]-s[l-1]==r-l+1)cout<<'I';
//             else cout<<'F';
// }
// int main()
// { 
//     cin>>t>>st;
//     n=1;
//     for(int i=1;i<=t;i++)n*=2;
//     for(int i=0;i
//         if(st[i]=='0')s[i+1]=s[i];
//         else s[i+1]=s[i]+1;
//     }
//     doit(1,n);
//     return 0;
// }


//作者:垫底抽風 链接:https://www.acwing.com/solution/content/128661/

420. 火星人

人类终于登上了火星的土地并且见到了神秘的火星人。

人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。

这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。

火星人用一种非常简单的方式来表示数字——掰手指。

火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 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

y总
next_permutation()会取得[first,last)所标示之序列的下一个排列组合序列【按字典序】
题意:找出对应的全排列按字典序编号 ==> 编号 + x ==> 新的编号 ==> 返回对应编号的排列


#include 
#include 
#include 

using namespace std;

const int N = 10010;

int n, m;
int q[N];

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
    //相对相差m轮    
    while (m -- ) next_permutation(a, a + n);//按字典序排列组合,找到与a序列相差m个编号的序列, 数组[a,a+n]变成该序列
    
    for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);
    
    return 0;
}


这里我们考虑一下next_permutation函数的原理,然后手动实现一遍。

对于给定的某个排列,我们想求出比它大的最小的排列。
可以从后往前遍历这个排列,找到第一个可以让排列的字典序变大的位置。

只有当序列单调下降时,它才不存在更大的排列,因此我们要找的位置就是第一次出现 a k − 1 < a k a_{k−1}ak1<ak 的位置。

那么此时将 a k − 1 a_{k−1} ak1 变成比它大的最小数,然后将剩余部分从小到大排序,得到的排列就是比原排列大的最小排列了。

这里有个小优化:

由于 a k − 1 a_{k−1} ak1 后面的部分已经从大到小排好序,因此只需将其翻转,就可以得到从小到大排序的结果了,不需要使用 sort函数,时间效率可以降到线性。
时间复杂度
一共求 m 次next_permutation,每次需要 o(n) 的时间,因此总时间复杂度是 O(nm)。


#include 
#include 
#include 
#include 

using namespace std;

const int N = 10010;

int n, m;
int q[N];

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    while (m -- )
    {
        int k = n - 1;
        while (q[k - 1] > q[k]) k -- ;//后缀倒序则最大了,需改变高位
        k -- ;
        int t = k;
        while (t + 1 < n && q[t + 1] > q[k]) t ++ ;//从后往前找到第一个大于a[k]的位置,
        swap(q[t], q[k]);//交换a[k], a[t]使得a单调递减
        reverse(q + k + 1, q + n);//翻转后单调递增
    }

    for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);

    return 0;
}


2005

421. 陶陶摘苹果

陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 10 个苹果。

苹果成熟的时候,陶陶就会跑去摘苹果。

陶陶有个 30 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩到板凳上再试试。

现在已知 10 个苹果到地面的高度,以及陶陶把手伸直的时候能够达到的最大高度,请帮陶陶算一下她能够摘到的苹果的数目。

假设她碰到苹果,苹果就会掉下来。

输入格式
输入文件包括两行数据。

第一行包含 10 个 100 到 200 之间(包括 100 和 200)的整数(以厘米为单位)分别表示 10 个苹果到地面的高度,两个相邻的整数之间用一个空格隔开。

第二行只包括一个 100 到 120 之间(包含 100 和 120)的整数(以厘米为单位),表示陶陶把手伸直的时候能够达到的最大高度。

输出格式
输出文件包括一行,这一行只包含一个整数,表示陶陶能够摘到的苹果的数目。

输入样例:
100 200 150 140 129 134 167 198 200 111
110
输出样例:
5

#include

using namespace std;

int a[15];
int h;
int res;

int main()
{
    for(int i = 0; i < 10; i++) scanf("%d", &a[i]);
    scanf("%d", &h);
    for(int i = 0; i < 10; i++)
        if(h + 30 >= a[i]) res ++;
        
    printf("%d\n", res);
    
    return 0;
}


422. 校门外的树

某校大门外长度为 L 的马路上有一排树,每两棵相邻的树之间的间隔都是 1 米。

我们可以把马路看成一个数轴,马路的一端在数轴 0 的位置,另一端在 L 的位置;数轴上的每个整数点,即 0,1,2,……,L,都种有一棵树。

由于马路上有一些区域要用来建地铁。

这些区域用它们在数轴上的起始点和终止点表示。

已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。

现在要把这些区域中的树(包括区域端点处的两棵树)移走。

你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入格式
输入文件的第一行有两个整数 L 和 M,L 代表马路的长度,M 代表区域的数目,L 和 M 之间用一个空格隔开。

接下来的 M 行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出格式
输出文件包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

数据范围
1≤L≤10000,
1≤M≤100
输入样例:
500 3
150 300
100 200
470 471
输出样例:
298

区间合并思想 O(logn) 排序耗时-限制
NOIP普及组历年题目_第4张图片
在这里插入图片描述



//计算区间合并后的总元素个数sum  , res = n - sum + 1 【区间总元素个数 - 已合并区间元素总数 + 1】

#include 
#include 
#include 

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 10010;

int n, m;
PII q[N];

int main()
{
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < m; i++)
        scanf("%d%d", &q[i].x, &q[i].y);
    sort(q, q + m);//m个区间按左端点st排序 [习惯性写成n,调试半天。。。]
      
    int sum = 0;
    int st = 0, ed = -1;//上一个的右端点ed 和 当前区间左端点st
    for(int i = 0; i < m ; i++)
        if(ed < q[i].x)//上一个的右端点ed < 当前区间左端点st ==> 没有交集直接加入sum
        {
            sum += ed - st + 1;//加上区间的元素个数
            st = q[i].x, ed = q[i].y; //依次判断每一个区间 左端点是否比上一个区间的右端点小【小则无交集,反之有交集】
        }
        else ed = max(ed, q[i].y); //有交集,区间合并: [st_1,ed_max] == [前一个区间左端点, 两个区间右端点取较大的]
    
    sum += ed - st + 1;//最后一个区间没有后继区间判断, 直接加
    
    printf("%d", n + 1 - sum);
        
    return 0;
}

暴力法


#include 
#include 
#include 

using namespace std;

const int N = 10010;

int L, M;
bool st[N];//被挖走为 true

int main()
{
    cin >> L >> M;
    int l, r;
    while(M --)
    {
        scanf("%d%d", &l, &r);
        for(int i = l; i <= r; i++)
            st[i] = true;
    }
    
    int res = 0;
    for(int i = 0;i <= L; i++)
        if(!st[i]) res ++;
    
    printf("%d\n", res);
    
    return 0;
}

遍历一遍[0, L] : 存区间左右端点,判断x是否在这些区间中 O(n)

#include 
#include 
#include 

using namespace std;

const int N = 110;

int n, m, l[N], r[N];//存区间左右端点

bool check(int x){//x是否在这些区间上
    for (int i = 0; i < m; i ++ )
        if (x >= l[i] && x <= r[i])
            return false;
    return true;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i ++ )
        cin >> l[i] >> r[i];
    int res = 0;
    for (int i = 0; i <= n; i ++ )
        res += check(i);//第i颗数在这些区间上返回true : 即 res += 1 
    cout << res << endl;
    return 0;
}

423. 采药

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。

为此,他想拜附近最有威望的医师为师。

医师为了判断他的资质,给他出了一个难题。

医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式
输入文件的第一行有两个整数 T 和 M,用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。

接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

数据范围
1≤T≤1000,
1≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3

424. 循环

乐乐是一个聪明而又勤奋好学的孩子。

他总喜欢探求事物的规律。

一天,他突然对数的正整数次幂产生了兴趣。

众所周知,2 的正整数次幂最后一位数总是不断的在重复 2,4,8,6,2,4,8,6…
我们说 2 的正整数次幂最后一位的循环长度是 4(实际上 4 的倍数都可以说是循环长度,但我们只考虑最小的循环长度)。

类似的,其余的数字的正整数次幂最后一位数也有类似的循环现象:

循环	|循环长度

-------- | -----
|

这时乐乐的问题就出来了:是不是只有最后一位才有这样的循环呢?对于一个整数 n 的正整数次幂来说,它的后 k 位是否会发生循环?如果循环的话,循环长度是多少呢?

注意: 
1.如果 n 的某个正整数次幂的位数不足 k,那么不足的高位看做是 0。
2.如果循环长度是 L,那么说明对于任意的正整数 a,n 的 a 次幂和 a+L 次幂的最后 k 位都相同。

输入格式
输入文件只有一行,包含两个整数 n 和 k,n 和 k 之间用一个空格隔开,表示要求 n 的正整数次幂的最后 k 位的循环长度。

输出格式
输出文件包括一行,这一行只包含一个整数,表示循环长度。

如果循环不存在,输出 −1。

数据范围
1≤n≤10100,
1≤k≤100
输入样例:
32 2
输出样例:
4

2006

425. 明明的随机数

明明想在学校中请一些同学一起做一项问卷调查。

为了实验的客观性,他先用计算机生成了 N 个 1 到 1000 之间的随机整数,对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。

然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。

请你协助明明完成“去重”与“排序”的工作。

输入格式
输入文件包含 2 行,第 1 行为 1 个正整数,表示所生成的随机数的个数:N。

第 2 行有 N 个用空格隔开的正整数,为所产生的随机数。

输出格式
输出文件也是 2 行,第 1 行为 1 个正整数 M,表示不相同的随机数的个数。

第 2 行为 M 个用空格隔开的正整数,为从小到大排好序的不相同的随机数。

数据范围
1≤N≤100
输入样例:
10
20 40 32 67 40 20 89 300 400 15
输出样例:
8
15 20 32 40 67 89 300 400

set的用法

insert(x), erase() , size() , conut(x)
创建iterator迭代器指针遍历 [begin(), end()]

#include
#include   //insert(x), erase() , size() , conut(x)

using namespace std;

int n;
set<int> q; 

int main()
{
    scanf("%d", &n);
    int x;
    for(int i = 0; i < n; i++) 
    {
        scanf("%d", &x);
        q.insert(x);
    }
    
    cout << q.size() << endl;
    
    for(auto t: q) 
        cout << t << " ";
    
    return 0;
}

//创建迭代器指针遍历 [begin(), end()]
//  for (set::iterator it = q.begin(); it != q.end(); it ++)
//      cout << *it << ' ';

426. 开心的金明

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N 元钱就行”。

今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 N 元。

于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1∼5 表示,第 5 等最重要。

他还从因特网上查到了每件物品的价格(都是整数元)。

他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第 j 件物品的价格为 v[j],重要度为 w[j],共选中了 k 件物品,编号依次为 j1,j2,…,jk,则所求的总和为:

v [ j 1 ] × w [ j 1 ] + v [ j 2 ] × w [ j 2 ] + … + v [ j k ] × w [ j k ] v[j1]×w[j1]+v[j2]×w[j2]+…+v[jk]×w[jk] v[j1]×w[j1]+v[j2]×w[j2]++v[jk]×w[jk]
请你帮助金明设计一个满足要求的购物单。

输入格式
输入文件的第 1 行,为两个正整数 N 和 m,用一个空格隔开。(其中 N 表示总钱数,m 为希望购买物品的个数)

从第 2 行到第 m+1 行,第 j 行给出了编号为 j−1 的物品的基本数据,每行有 2 个非负整数 v 和 p。(其中 v 表示该物品的价格,p 表示该物品的重要度)

输出格式
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 1 0 8 10^8 108)。

数据范围
1≤N<30000,
1≤m<25,
0≤v≤10000,
1≤p≤5
输入样例:
1000 5
800 2
400 5
300 5
400 3
200 2
输出样例:
3900

427. Jam的计数法

Jam 是个喜欢标新立异的科学怪人。

他不使用阿拉伯数字计数,而是使用小写英文字母计数,他觉得这样做,会使世界更加丰富多彩。

在他的计数法中,每个数字的位数都是相同的(使用相同个数的字母),英文字母按原先的顺序,排在前面的字母小于排在它后面的字母。

我们把这样的“数字”称为 Jam 数字。

在 Jam 数字中,每个字母互不相同,而且从左到右是严格递增的。

每次,Jam 还指定使用字母的范围,例如,从 2 到 10,表示只能使用 {b,c,d,e,f,g,h,i,j} 这些字母。

如果再规定位数为 5,那么,紧接在 Jam 数字 bdfij 之后的数字应该是 bdghi

如果我们用 U、V 依次表示 Jam 数字 bdfijbdghi,则 U

你的任务是:对于从文件读入的一个 Jam 数字,按顺序输出紧接在后面的 5 个 Jam 数字,如果后面没有那么多 Jam 数字,那么有几个就输出几个。

输入格式
输入文件有 2 行,第 1 行为 3 个正整数,用一个空格隔开:s,t,w (其中 s 为所使用的最小的字母的序号,t 为所使用的最大的字母的序号,w 为数字的位数。)

第 2 行为具有 w 个小写字母的字符串,为一个符合要求的 Jam 数字。

所给的数据都是正确的,不必验证。

输出格式
输出文件最多为 5 行,为紧接在输入的 Jam 数字后面的 5 个 Jam 数字,如果后面没有那么多 Jam 数字,那么有几个就输出几个。

每行只输出一个 Jam 数字,是由 w 个小写字母组成的字符串,不要有多余的空格。

数据范围
1≤s 2≤w≤t−s
输入样例:
2 10 5
bdfij
输出样例:
bdghi
bdghj
bdgij
bdhij
befgh

428. 数列

给定一个正整数 k,把所有 k 的方幂及所有有限个互不相等的 k 的方幂之和构成一个递增的序列,例如,当 k=3 时,这个序列是:

1,3,4,9,10,12,13,…
该序列实际上就是: 3 0 , 3 1 , 3 0 + 3 1 , 3 2 , 3 0 + 3 2 , 3 1 + 3 2 , 3 0 + 3 1 + 3 2 , … 3^0,3^1,3^0+3^1,3^2,3^0+3^2,3^1+3^2,3^0+3^1+3^2,… 303130+313230+3231+3230+31+32
请你求出这个序列的第 N 项的值(N 用 10 进制数表示,从 1 开始)。

例如,对于 k=3,N=100,正确答案应该是 981。

输入格式
输入文件只有 1 行,为 2 个正整数,用一个空格隔开:k,N。

输出格式
输出文件为计算结果,是一个正整数(在所有的测试数据中,结果均不超过 2.1× 1 0 9 10^9 109)。(整数前不要有空格和其他符号)。

数据范围
3≤k≤15,
10≤N≤1000
输入样例:
3 100
输出样例:
981

二进制数映射 : 有 k n k^n kn ==> 从低到高位的第n位为1, 反之为0
k m > k m − 1 + k m − 2 + . . . + k 1 k^m > k^{m-1} + k_{m-2} + ... + k_1 km>km1+km2+...+k1 ==> 用映射后的二进制数看高位1的位置,可比较大小
求第n项只需转n对应二进制数 ==> 枚举n二进制数每位选取第i位是1的 k i k^i ki加到res ==>res即为第n项数值
n >> i & 1取n的二进制的第i位


#include 
#include 
#include 
#include 

using namespace std;

int k, n;

int qmi(int a, int k)//因为题目数值限定在int内,不需要LL
{
    int res = 1;//乘积基数1
    while(k)
    {
        if(k % 2) res *= a;
        a *= a;
        k >>= 1;
    }
    return res;
}


int main()
{
    cin >> k >> n;//第n项==>映射n的二进制数
    int res = 0;
    for(int i = 0; i < 10; i++) //枚举二进制数每位【选或不选】 2^10项 > 1000项 
    {
        if (n >> i & 1)//为1则选取k^i ,0则不选, 算出二进制数对应的数值
            res += qmi(k, i);
    }
    cout << res << endl;
    return 0;
}

2007

429. 奖学金

某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 5 名学生发奖学金。

期末,每个学生都有 3 门课的成绩:语文、数学、英语。

先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学排在前面,这样,每个学生的排序是唯一确定的。

任务:先根据输入的 3 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名学生的学号和总分。

注意,在前 5 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。

例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:

7 279
5 279
这两行数据的含义是:总分最高的两个同学的学号依次是 7 号、5 号。

这两名同学的总分都是 279 (总分等于输入的语文、数学、英语三科成绩之和),但学号为 7 的学生语文成绩更高一些。

如果你的前两名的输出数据是:

5 279
7 279
则按输出错误处理。

输入格式
输入文件包含 n+1 行:

第 1 行为一个正整数 n,表示该校参加评选的学生人数。

第 2 到 n+1 行,每行有 3 个用空格隔开的数字,每个数字都在 0 到 100 之间,第 j 行的 3 个数字依次表示学号为 j−1 的学生的语文、数学、英语的成绩。

每个学生的学号按照输入顺序编号为 1∼n (恰好是输入数据的行号减 1)。

所给的数据都是正确的,不必检验。

输出格式
输出文件共有 5 行,每行是两个用空格隔开的正整数,依次表示前 5 名学生的学号和总分。

数据范围
6≤n≤300
输入样例:
6
90 67 80
87 66 91
78 89 91
88 99 77
67 89 64
78 89 98
输出样例:
6 265
4 264
3 258
2 244
1 237

多关键字排序

#include
#include


using namespace std;

const int N = 310;

int n;
struct Student
{
    int id, x, y, z, sum;
    
    bool operator> (const Student& t)const//按总分从大到小,若总分相同则按语文从大到小,若语文也相同则学号小的放前面
    {
        int sum = x + y + z, t_sum = t.x + t.y + t.z;//可以简化代码
        if(sum != t_sum) return sum > t_sum;
        if(x != t.x) return x > t.x;
        return id < t.id;
    }
    
}stu[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        stu[i].id = i;
        scanf("%d%d%d", &stu[i].x, &stu[i].y, &stu[i].z);
        stu[i].sum = stu[i].x + stu[i].y + stu[i].z;
    }
    
    sort(stu + 1, stu + n + 1, greater<Student>());//从1开始 stu元素有初始值为0
    
    for(int i = 1; i <= 5; i++)
        printf("%d %d\n",stu[i].id, stu[i].sum);
            
    return 0;
}


// for (int i = 1; i <= 5; i ++ )
// {
//     auto& t = stu[i];
//     cout << t.id << ' ' << t.sum << endl;
// }

430. 纪念品分组

元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。

为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品,并且每组纪念品的价格之和不能超过一个给定的整数。

为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。

你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

输入格式
输入文件包含 n+2 行:

第 1 行包括一个整数 w,为每组纪念品价格之和的上限。

第 2 行为一个整数 n,表示购来的纪念品的总件数。

第 3−n+2 行每行包含一个正整数 Pi,表示所对应纪念品的价格。

输出格式
输出文件仅一行,包含一个整数,即最少的分组数目。

数据范围
1≤n≤30000,
80≤w≤200,
5≤Pi≤w
输入样例:
100
9
90
20
20
30
50
60
70
80
90
输出样例:
6

431. 守望者的逃离

恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。

守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。

为了杀死守望者,尤迪安开始对这个荒岛施咒,这座岛很快就会沉下去。

到那时,岛上的所有人都会遇难。

守望者的跑步速度为 17m/s,以这样的速度是无法逃离荒岛的。

庆幸的是守望者拥有闪烁法术,可在 1s 内移动 60m,不过每次使用闪烁法术都会消耗魔法值 10 点。

守望者的魔法值恢复的速度为 4 点/s,只有处在原地休息状态时才能恢复。

现在已知守望者的魔法初值 M,他所在的初始位置与岛的出口之间的距离 S,岛沉没的时间 T。

你的任务是写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望
者在剩下的时间能走的最远距离。

注意:守望者跑步、闪烁或休息活动均以秒(s)为单位,且每次活动的持续时间为整数秒。距离的单位为米(m)。

输入格式
输入文件仅一行,包括空格隔开的三个非负整数 M,S,T。

输出格式
输出文件包括两行:

第 1 行为字符串 Yes 或 No(区分大小写),即守望者是否能逃离荒岛。

第 2 行包含一个整数,第一行为 Yes 时表示守望者逃离荒岛的最短时间;第一行为 No 时表示守望者能走的最远距离。

数据范围
1≤T≤300000,
0≤M≤1000,
1≤S≤108
输入样例:
39 200 4
输出样例:
No
197
难度:简单
时/空限制:1s / 64MB
总通过数:448
总尝试数:1052
来源:NOIP2007普及组
算法标签

432. Hanoi双塔问题

给定 A,B,C 三根足够长的细柱,在 A 柱上放有 2n 个中间有空的圆盘,共有 n 个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为 n=3 的情形)。

NOIP普及组历年题目_第5张图片

现要将这些圆盘移到 C 柱上,在移动过程中可放在 B 柱上暂存。要求:

每次只能移动一个圆盘;
A、B、C 三根细柱上的圆盘都要保持上小下大的顺序;
任务:设 An 为 2n 个圆盘完成上述任务所需的最少移动次数,对于输入的 n,输出 An。

输入格式
输入文件为一个正整数 n,表示在 A 柱上放有 2n 个圆盘。

输出格式
输出文件仅一行,包含一个正整数,为完成上述任务所需的最少移动次数 An。

数据范围
1≤n≤200
输入样例:
2
输出样例:
6

本质就是几个单调栈。

2008

433. ISBN号码

434. 排座椅

435. 传球游戏

436. 立体图

2009

437. 多项式输出

438. 分数线划定

439. 细胞分裂

440. 道路游戏

2010

441. 数字统计

442. 接水问题

443. 导弹拦截

444. 三国游戏

2011

445. 数字反转

446. 统计单词数

447. 瑞士轮

448. 表达式的值

2012

449. 质因数分解

450. 寻宝

451. 摆花

452. 文化之旅

2013

453. 计数问题

454. 表达式求值

455. 小朋友的数字

456. 车站分级

2014

457. 珠心算测验

458. 比例简化

459. 螺旋矩阵

460. 子矩阵

2015

461. 金币

462. 扫雷游戏

463. 求和

464. 推销员

2016

465. 买铅笔

466. 回文日期

467. 海港

468. 魔法阵

2017

469. 成绩

470. 图书管理员

471. 棋盘

472. 跳房子

2018

473. 标题统计

474. 龙虎斗

475. 摆渡车

476. 对称二叉树

2019

1161. 数字游戏

1162. 公交换乘

1163. 纪念品

1164. 加工零件

2020

2767. 优秀的拆分

2768. 直播获奖

2769. 表达式

2770. 方格取数

2021

4086. 分糖果

4087. 插入排序

4088. 网络连接

4089. 小熊的果篮

你可能感兴趣的:(合集,算法,图论,c++)