【NOIP2018初赛部分题目解析】

记录一些初赛题目,主要是我做错了的和卡了我一会的,还有就是我认为自己可能在考场上会做错的,题目是直接在luogu上找的。

下面的部分解答包含了博主自己的yy和毒奶,无视就好

2018:

说实话18年初赛难度已经有点大了,坑也有点多。在洛谷做的时候最后一道题变量名敲错了,不然就AK了
在这里插入图片描述
在这里插入图片描述
由于是笔试,没有编译器来check差评

7.在一条长度为 1 的线段上随机取两个点,则以这两个点为端点的线段的期望长度是( )。

A. 1 / 2
B. 1 / 3
C. 2 / 3
D. 3 / 5
正确答案: B

听说去年很多人这道题都是瞎蒙蒙对了答案的。然而我是因为知道这个结论
为什么初赛会考微积分 实际上考虑两个端点的位置,就是求 ∫ 0 1 ∫ 0 1 a b s ( x − y ) d y d x = 2 ⋅ ∫ 0 1 ∫ 0 x ( x − y ) d y d x = 2 ⋅ ∫ 0 1 x 2 2 d x = 1 3 \int_{0}^{1}\int_{0}^1abs(x-y)\mathrm{d}y\mathrm{d}x\\=2\cdot\int_{0}^{1}\int_{0}^x(x-y)\mathrm{d}y\mathrm{d}x\\=2\cdot\int_{0}^{1}\frac{x^2}{2}\mathrm{d}x=\frac{1}{3} 0101abs(xy)dydx=2010x(xy)dydx=2012x2dx=31

求积分即可。除此以外不知道还有什么对的做法,连续过程的期望计算显然只能用微积分。

17.方程 a*b = (a or b) * (a and b),在 a, b 都取 [0, 31] 中的整数时,共有_____组解。(*表示乘法;or 表示按位或运算;and 表示按位与运算)

正确答案: 454

容易注意到一个东西 a & b ≤ min ⁡ ( a , b ) ≤ max ⁡ ( a , b ) ≤ a ∣ b < 2 ⋅ max ⁡ ( a , b ) a\&b\leq \min(a,b)\leq \max(a,b)\leq a|b < 2\cdot \max(a,b) a&bmin(a,b)max(a,b)ab<2max(a,b)

根据这个结论,稍微分类讨论一下发现上面的方程成立当且仅当 { a & b = min ⁡ ( a , b ) a ∣ b = max ⁡ ( a , b ) \left\{\begin{aligned}a\&b=\min(a,b)\\a|b=\max(a,b)\end{aligned}\right. {a&b=min(a,b)ab=max(a,b),换句话说,某一个数二进制表示应当是另一个的子集。
则答案为 2 × ( ∑ i = 0 5 ( 5 i ) × 2 i ) − 32 = 454 2×(\sum\limits_{i=0}^5{5\choose i}×2^i)−32=454 2×(i=05(i5)×2i)32=454

  1. 阅读程序写结果:
#include <iostream>
using namespace std;
const int N = 110;
bool isUse[N];
int n, t;
int a[N], b[N];
bool isSmall() {
    for (int i = 1; i <= n; ++i)
        if (a[i] != b[i]) return a[i] < b[i];
    return false;
}
bool getPermutation(int pos) {
    if (pos > n) {
        return isSmall();
    }
    for (int i = 1; i <= n; ++i) {
        if (!isUse[i]) {
            b[pos] = i; isUse[i] = true;
            if (getPermutation(pos + 1)) {
                return true;
            }
            isUse[i] = false;
        }
    }
    return false;
}
void getNext() {
    for (int i = 1; i <= n; ++i) {
        isUse[i] = false;
    }
    getPermutation(1);
    for (int i = 1; i <= n; ++i) {
        a[i] = b[i];
    }

}
int main() {
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= t; ++i) {
        getNext();
    }
    for (int i = 1; i <= n; ++i) {
        printf("%d", a[i]);
        if (i == n) putchar(’\n’); else putchar(’ ');
    }
    return 0;
}

输入1:6 10 1 6 4 5 3 2
正确答案: 2 1 3 5 6 4
输入2:6 200 1 5 3 4 2 6
正确答案: 3 2 5 6 1 4

说实话真的没有想到CCF的代码效率这么低下,连剪枝都没有。。。

实际上就是给你一个长度为 n n n的排列然后让你算出 t t t个之后的排列。
直接康托展开然后逆展开就行了。去年不会康托展开第二个问题算错了

23.完善程序

一只小猪要买 N 件物品(N 不超过 1000)。

它要买的所有物品在两家商店里都有卖。第 i 件物品在第一家商店的价格是 a[i] ,在第二家商店的价格是 b[i] ,两个价格都不小于 0 且不超过 10000。如果在第一家商店买的物品的总额不少于 50000,那么在第一家店买的物品都可以打 95 折(价格变为原来的 0.95 倍)。

求小猪买齐所有物品所需最少的总额。

输入:第一行一个数 N。接下来N 行,每行两个数。第 i 行的两个数分别代表 a[i],b[i]。

输出:输出一行一个数,表示最少需要的总额,保留两位小数。

试补全程序。

#include <cstdio>
#include <cstdlib>
using namespace std;

const int Inf = 1000000000;
const int threshold = 50000;
const int maxn = 1000;

int n, a[maxn], b[maxn];
bool put_a[maxn];
int total_a, total_b;

double ans;
int f[threshold];

int main() {
    scanf("%d", &n);
    total_a = total_b = 0;
    for (int i = 0; i < n; ++i) {
        scanf("%d%d", a + i, b + i);
        if (a[i] <= b[i]) total_a += a[i];
        else total_b += b[i];
    }
    ans = total_a + total_b;
    total_a = total_b = 0;
    for (int i = 0; i < n; ++i) {
        if (____(1)____) {//a[i] * 0.95 <= b[i]
            put_a[i] = true;
            total_a += a[i];
        } else {
            put_a[i] = false;
            total_b += b[i];
        }
    }
    if (____(2)____) {//total_a >= threshold
        printf("%.2f", total_a * 0.95 + total_b);
        return 0;
    }
    f[0] = 0;
    for (int i = 1; i < threshold; ++i)
        f[i] = Inf;
    int total_b_prefix = 0;
    for (int i = 0; i < n; ++i)
        if (!put_a[i]) {
            total_b_prefix += b[i];
            for (int j = threshold - 1; j >= 0; --j) {
                if (____(3)____>= threshold && f[j] != Inf)//total_a + j + a[i]
                    ans = min(ans, (total_a + j + a[i]) * 0.95 +____(4)____);
                    //f[j] + total_b - total_b_prefix
                f[j] = min(f[j] + b[i], j >= a[i] ?____(5)____: Inf);
                //f[j - a[i]]
            }
        }
    printf("%.2f", ans);
    return 0;
}

答案见代码注释

实际上是一个很奇怪的DP+贪心。

首先可以看出第二个空的答案,同时我们知道这时要求能放到 a a a的都要尽量放到 a a a里面。于是可以看出第一个空的比较要乘上 0.95 0.95 0.95

看到第三空,我们根据后面比较的是threshold可以看出这里应该放的是 a a a里面买的东西的价格,下面乘上 0.95 0.95 0.95的显然就是了,直接抄上来即可。由于这里更新了答案,我们可以推断出第四空要填的是这个情况下 b b b里面买的东西的最小价格,但是目前还不知道 f f f维护了个什么所以填不出来。

看到第五空,根据min的第一个参数可以知道 f [ j ] f[j] f[j]求的是 a a a里面放了价格为 j j j的物品的时候, b b b里面最少要放多少。那么直接得到第五空的答案,然后回去把第四空填好就行了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值