NOIP2016普及组解题报告

概述

\(NOIP2016\)普及组的前三题都比较简单,第四题也有很多的暴力分,相信参加了的各位\(OIer\)\(2016\)年都取得了很好的成绩。

那么,我将会分析\(NOIP2016\)普及组的四道题目,希望对各位的\(OI\)学习有所帮助!

T1. 买铅笔

题目描述

\(P\)老师需要去商店买\(n\)支铅笔作为小朋友们参加\(NOIP\)的礼物。她发现商店一共有\(3\)种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起见,\(P\)老师决定只买同一种包装的铅笔。

商店不允许将铅笔的包装拆开,因此\(P\)老师可能需要购买超过\(n\)支铅笔才够给小朋友们发礼物。

现在\(P\)老师想知道,在商店每种包装的数量都足够的情况下,要买够至少\(n\)支铅笔最少需要花费多少钱。

输入格式

第一行包含一个正整数\(n\),表示需要的铅笔数量。

接下来三行,每行用\(2\)个正整数描述一种包装的铅笔:其中第\(1\)个整数表示这种包装内铅笔的数量,第\(2\)个整数表示这种包装的价格。

保证所有的\(7\)个数都是不超过\(10000\)的正整数。

输出格式

\(1\)个整数,表示\(P\)老师最少需要花费的钱。

输入输出样例

样例1

输入样例#1:

57
2 2
50 30
30 27

输出样例#1:

54

样例2

输入样例#2:

9998
128 233
128 2333
128 666

输出样例#2:

18407

样例3

输入样例#3:

9999
101 1111
1 9999
1111 9999

输出样例#3:

89991

说明

铅笔的三种包装分别是:

  • \(2\)支装,价格为\(2\);
  • \(50\)支装,价格为\(30\);
  • \(30\)支装,价格为\(27\)

\(P\)老师需要购买至少\(57\)支铅笔。

如果她选择购买第一种包装,那么她需要购买\(29\)份,共计\(2 \times 29 = 58\)支,需要花费的钱为\(2 \times 29 = 58\)

实际上,\(P\)老师会选择购买第三种包装,这样需要买\(2\)份。虽然最后买到的铅笔数量更多了,为\(30 \times 2 = 60\)支,但花费却减少为\(27 \times 2 = 54\),比第一种少。

对于第二种包装,虽然每支铅笔的价格是最低的,但要够发必须买\(2\)份,实际的花费达到了 \(30 \times 2 = 60\),因此\(P\)老师也不会选择。

所以最后输出的答案是\(54\)

题解

\(T1\)总是这么水。

直接暴力判断每种情况,最后输出最小值即可。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return f * x;
}

int a, b, n, c, a1, b1, c1, s1, s2, s3, k = 2147483647;

int main()
{
    n = gi(), a = gi(), a1 = gi(), b = gi(), b1 = gi(), c = gi(), c1 = gi();
    if (n % a == 0)
        s1 = a1 * (n / a);
    if (n % b == 0)
        s2 = b1 * (n / b);
    if (n % c == 0)
        s3 = c1 * (n / c);
    if (n % a != 0)
        s1 = a1 * (n / a + 1);
    if (n % a != 0)
        s2 = b1 * (n / b + 1);
    if (n % a != 0)
        s3 = c1 * (n / c + 1);
    if (k > s1)
        k = s1;
    if (k > s2)
        k = s2;
    if (k > s3)
        k = s3;
    printf("%d\n", k);
    return 0;
}

T2. 回文日期

题目描述

在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。

牛牛习惯用\(8\)位数字表示一个日期,其中,前\(4\)位代表年份,接下来\(2\)位代表月份,最后\(2\)位代表日期。显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。

牛牛认为,一个日期是回文的,当且仅当表示这个日期的\(8\)位数字是回文的。现在,牛牛想知道:在他指定的两个日期之间包含这两个日期本身),有多少个真实存在的日期是回文的。

一个\(8\)位数字是回文的,当且仅当对于所有的\(i ( 1 \le i \le 8)\)从左向右数的第\(i\)个数字和第\(9-i\)个数字(即从右向左数的第\(i\)个数字)是相同的。

例如:

  • 对于\(2016\)\(11\)\(19\)日,用\(8\)位数字\(20161119\)表示,它不是回文的。

  • 对于\(2010\)\(1\)\(2\)日,用\(8\)位数字\(20100102\)表示,它是回文的。

  • 对于\(2010\)\(10\)\(2\)日,用\(8\)位数字\(20101002\)表示,它不是回文的。

每一年中都有\(12\)个月份:

其中,\(1,3,5,7,8,10,12\)月每个月有\(31\)天;\(4,6,9,11\)月每个月有\(30\)天;而对于\(2\)月,闰年时有\(29\)天,平年时有\(28\)天。

一个年份是闰年当且仅当它满足下列两种情况其中的一种:

  1. 这个年份是\(4\)的整数倍,但不是\(100\)的整数倍;

  2. 这个年份是\(400\)的整数倍。

例如:

  • 以下几个年份都是闰年:\(2000,2012,2016\)

  • 以下几个年份是平年:\(1900,2011,2014\)

输入格式

两行,每行包括一个\(8\)位数字。

第一行表示牛牛指定的起始日期。

第二行表示牛牛指定的终止日期。

保证\(date\_i\)和都是真实存在的日期,且年份部分一定为\(4\)位数字,且首位数字不为\(0\)

保证\(date1\)一定不晚于\(date2\)

输出格式

一个整数,表示在\(date1\)\(date2\)之间,有多少个日期是回文的。

输入输出样例

样例1

输入样例#1:

20110101
20111231

输出样例#1:

1

样例2

输入样例#2:

20000101
20101231

输出样例#2:

2

说明

样例说明

对于样例\(1\),符合条件的日期是\(20111102\)

对于样例\(2\),符合条件的日期是\(20011002\)\(20100102\)

子任务

对于\(60\%\)的数据,满足\(date1 = date2\)

题解

\(T2\)也很简单

直接枚举年份,然后依次暴力判断即可。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return f * x;
}

int date1, date2, i, j, k, l, a, b, a1, b1;

inline int hw(int m)
{
    int n = 0;
    while (m != 0)
    {
        n = n * 10 + m % 10;
        m /= 10;
    }
    return n;
}

inline int pd(int m, int n)
{
    int y = m / 100, r = m % 100;
    if (y <= 0 || y > 12)
        return 0;
    if (r <= 0 || r > 31)
        return 0;
    if ((y == 4 || y == 6 || y == 9 || y == 11) && r > 30)
        return 0;
    if (y == 2 && r > 29)
        return 0;
    return 1;
}

int main()
{
    date1 = gi(), date2 = gi();
    a = date1 / 10000;
    a1 = date1 % 10000;
    b = date2 / 10000;
    b1 = date2 % 10000;
    if (date1 == date2)
    {
        date1 = date1 % 10000;
        date2 = date2 / 10000;
        for (i = 1; i <= 4; i++)
        {
            l = l * 10 + date1 % 10;
            date1 /= 10;
        }
        if (l == date2)
        {
            printf("1");
            return 0;
        }
        else
        {
            printf("0");
            return 0;
        }
    }
    if (hw(a) >= a1 && pd(hw(a), a))
        k++;
    if (hw(b) <= b1 && pd(hw(b), b) && a != b)
        k++;
    for (i = a + 1; i < b; i++)
    {
        if (pd(hw(i), i))
            k++;
    }
    printf("%d\n", k);
    return 0;
}

T3. 海港

题目描述

\(小K\)是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客。

\(小K\)对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况;对于第i艘到达的船,他记录了这艘船到达的时间\(t_{i}\) (单位:秒),船上的乘 客数\(k_i\)​,以及每名乘客的国籍\(x_{i,1}, x_{i,2},…,x_{i,k}\)

\(小K\)统计了\(n\)艘船的信息,希望你帮忙计算出以每一艘船到达时间为止的\(24\)小时(\(24\)小时\(=\)\(86400\)秒)内所有乘船到达的乘客来自多少个不同的国家。

形式化地讲,你需要计算\(n\)条信息。对于输出的第\(i\)条信息,你需要统计满足\(t_i-86400<t_p \le t_i\)​的船只\(p\),在所有的\(x_{p,j}\)​中,总共有多少个不同的数。

输入输出格式

输入格式

第一行输入一个正整数\(n\),表示\(小K\)统计了\(n\)艘船的信息。

接下来\(n\)行,每行描述一艘船的信息:前两个整数\(t_i\)​和\(k_i\)​分别表示这艘船到达海港的时间和船上的乘客数量,接下来\(k_i\)​个整数\(x_{i,j}\)表示船上乘客的国籍。

保证输入的\(t_i\)​是递增的,单位是秒;表示从\(小K\)第一次上班开始计时,这艘船在第\(t_i\)秒到达海港。

输出格式

输出\(n\)行,第\(i\)行输出一个整数表示第\(i\)艘船到达后的统计信息。

输入输出样例

样例1

输入样例#1:

3
1 4 4 1 2 2
2 2 2 3
10 1 3

输出样例#1:

3
4
4

样例2

输入样例#2:

4
1 4 1 2 2 3
3 2 2 3
86401 2 3 4
86402 1 5

输出样例#2:

3
3
3
4

说明

样例解释1

第一艘船在第\(1\)秒到达海港,最近\(24\)小时到达的船是第一艘船,共有\(4\)个乘客, 分别是来自国家\(4,1,2,2\),共来自\(3\)个不同的国家;

第二艘船在第\(2\)秒到达海港,最近\(24\)小时到达的船是第一艘船和第二艘船,共有\(4 + 2 = 6\)个乘客,分别是来自国家\(4,1,2,2,2,3\),共来自\(4\)个不同的国家;

第三艘船在第\(10\)秒到达海港,最近\(24\)小时到达的船是第一艘船、第二艘船和第 三艘船,共有\(4+ 2+1=7\)个乘客,分别是来自国家\(4,1,2,2,2,3,3\),共来自4个不同的国家。

样例解释2

第一艘船在第\(1\)秒到达海港,最近\(24\)小时到达的船是第一艘船,共有\(4\)个乘客,分别是来自国家\(1,2,2,3\),共来自\(3\)个不同的国家。

第二艘船在第\(3\)秒到达海港,最近\(24\)小时到达的船是第一艘船和第二艘船,共有\(4+2=6\)个乘客,分别是来自国家\(1,2,2,3,2,3\),共来自\(3\)个不同的国家。

第三艘船在第\(86401\)秒到达海港,最近\(24\)小时到达的船是第二艘船和第三艘船,共有\(2+2=4\)个乘客,分别是来自国家\(2,3,3,4\),共来自\(3\)个不同的国家。

第四艘船在第\(86402\)秒到达海港,最近\(24\)小时到达的船是第二艘船、第三艘船和第四艘船,共有\(2+2+1=5\)个乘客,分别是来自国家\(2,3,3,4,5\),共来自\(4\)个不同的国家。

数据范围

\(1 \le n \le 10^5\),$\sum{ki} \le 3*10^5 $ ,\(1\le x(i,j) \le 10^5\)\(1 \le t(i-1)\le ti \le 10^9\)

其中\(\sum{ki}\)表示所有的\(k_i\)​的和。

题解

\(T3\)是个模拟水题。

用队列维护当前时间内的乘客,并且统计出现过的国籍数,超过\(24h\)的就去掉。注意要一边输入一边统计。

最后输出即可。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return f * x;
}

int t[100010], p[100010], q[100010], w[500010], n, s, head, tail, rs, i, j, k, l, r;

int main()
{
    n = gi();
    p[1] = 1;
    head = 1;
    for (i = 1; i <= n; i++)
    {
        t[i] = gi(), r = gi();
        p[i + 1] = p[i] + r;
        for (j = 1; j <= r; j++)
        {
            if (!q[w[j + p[i] - 1] = gi()]++)
            {
                s++;
            }
        }
        while (t[i] - t[head] >= 86400)
        {
            for (k = p[head]; k < p[head + 1]; k++)
            {
                if (--q[w[k]] == 0)
                {
                    s--;
                }
            }
            head++;
        }
        printf("%d\n", s);
    }
    return 0;
}

T4. 魔法阵

题目描述

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。

大魔法师有\(m\)个魔法物品,编号分别为\(1,2,...,m\)。每个物品具有一个魔法值,我们用\(X_i\)​表示编号为\(i\)的物品的魔法值。每个魔法值\(X_i\)是不超过\(n\)的正整数,可能有多个物品的魔法值相同。

大魔法师认为,当且仅当四个编号为\(a,b,c,d\)的魔法物品满足\(x_a<x_b<x_c<x_d,X_b-X_a=2(X_d-X_c)\),并且\(x_b-x_a<(x_c-x_b)/3\)时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的\(A\)物品,\(B\)物品,\(C\)物品,\(D\)物品。

现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的\(A\)物品出现的次数,作为\(B\)物品的次数,作为\(C\)物品的次数,和作为\(D\)物品的次数。

输入输出格式

输入格式

第一行包含两个空格隔开的正整数\(n,m\)

接下来\(m\)行,每行一个正整数,第\(i+1\)行的正整数表示\(X_i\)​,即编号为\(i\)的物品的魔法值。

保证\(1 \le n \le 15000\),\(1 \le m \le 40000\),\(1 \le Xi \le n\)。每个\(X_i\)​是分别在合法范围内等概率随机生成的。

输出格式

\(m\)行,每行\(4\)个整数。第\(i\)行的\(4\)个整数依次表示编号为\(i\)的物品作为\(A,B,C,D\)物品分别出现的次数。

保证标准输出中的每个数都不会超过\(10^9\)。每行相邻的两个数之间用恰好一个空格隔开。

输入输出样例

样例1

输入样例#1:

30 8
1
24
7
28
5
29
26
24

输出样例#1:

4 0 0 0
0 0 1 0
0 2 0 0
0 0 1 1
1 3 0 0
0 0 0 2
0 0 2 2
0 0 1 0

样例2

输入样例#2:

15 15
1 
2 
3 
4 
5
6 
7 
8 
9
10
11
12
13
14
15

输出样例#2:

5 0 0 0
4 0 0 0
3 5 0 0
2 4 0 0
1 3 0 0
0 2 0 0
0 1 0 0
0 0 0 0
0 0 0 0
0 0 1 0
0 0 2 1
0 0 3 2
0 0 4 3
0 0 5 4
0 0 0 5

说明

样例解释1

共有\(5\)个魔法阵,分别为:

物品\(1,3,7,6\),其魔法值分别为\(1,7,26,29\);

物品\(1,5,2,7\),其魔法值分别为\(1,5,24,26\);

物品\(1,5,7,4\),其魔法值分别为\(1,5,26,28\);

物品\(1,5,8,7\),其魔法值分别为\(1,5,24,26\);

物品\(5,3,4,6\),其魔法值分别为\(5,7,28,29\)

以物品\(5\)为例,它作为\(A\)物品出现了\(1\)次,作为\(B\)物品出现了\(3\)次,没有作为\(C\)物品或者\(D\)物品出现,所以这一行输出的四个数依次为\(1,3,0,0\)

此外,如果我们将输出看作一个\(m\)\(4\)列的矩阵,那么每一列上的\(m\)个数之和都应等于魔法阵的总数。所以,如果你的输出不满足这个性质,那么这个输出一定不正确。你可以通过这个性质在一定程度上检查你的输出的正确性。

题解

这题纯暴力有\(40\)分,稍加优化就可以得到\(65\)分的高分。

正解其实是推公式,运用了数学知识。

公式在这里就不手推了,留给读者思考的空间,也可以参考网上的题解。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return f * x;
}

int s, n, m, num[40010], v[40010], a[40010], b[40010], c[40010], d[40010], i, j, l, k, t, va, vb, vc, vd;

int main()
{
    n = gi(), m = gi();
    for (i = 1; i <= m; i++)
    {
        v[i] = gi();
        num[v[i]]++;
    }
    for (t = 1; t * 9 < n; t++)
    {
        s = 0;
        for (vd = 9 * t + 2; vd <= n; vd++)
        {
            vc = vd - t;
            vb = vc - 6 * t - 1;
            va = vb - 2 * t;
            s = s + num[vb] * num[va];
            c[vc] = c[vc] + num[vd] * s;
            d[vd] = d[vd] + num[vc] * s;
        }
        s = 0;
        for (va = n - 9 * t - 1; va >= 1; va--)
        {
            vb = va + 2 * t;
            vc = vb + 6 * t + 1;
            vd = vc + t;
            s = s + num[vc] * num[vd];
            a[va] = a[va] + num[vb] * s;
            b[vb] = b[vb] + num[va] * s;
        }
    }
    for (i = 1; i <= m; i++)
    {
        printf("%d %d %d %d\n", a[v[i]], b[v[i]], c[v[i]], d[v[i]]);
    }
    return 0;
}

总结

这套题总体来说并不难,只要您合理分配时间、认真思考,就应该能得到\(100+100+100+40=340\)的高分(除了某些考场上手推数学公式的数学大佬\(T4\)可以拿满分)。

\(T1\)\(T2\)考察了选手的语言基础,只要细心就可以拿全\(200\)分。

\(T3\)考验了选手的耐心和分析能力,在处理细节方面要注意:细节决定成败!

\(T4\)就是推数学公式,暴力分也很多,考察了选手的数学能力。

希望大家在以后的\(OI\)道路上能够顺风顺水!

猜你喜欢

转载自www.cnblogs.com/xsl19/p/11104920.html