NOIP2021解题报告

1.报数

        像今年 T1 这种几年不遇的水题你是不可能再遇见第二次的。

        这道题提前预处理单纯地筛一筛就可以了,没有别的任何操作。

需要注意的是,在预处理时要合理的剪枝,保证时间复杂度控制在 O(10^7+T)10^7 是数据范围T是询问数)。

我们用f数组表示该数是否被标记。如果一个数i被标记过了,就直接跳过;如果i含有数字7,我们就将i的所有倍数(包括i本身)全部标记。

我们用 nx数组(也就是 next的缩写)来记录该数的下一个报的数是多少。在处理的时候,我们需要记录上一个报的数 ls(last 的缩写,也就是没有标记的数)。如果 i没有标记过也不含有数字 7,那么nx_{ls}就是 i,然后将 ls 更新为 i。

预处理的好处就是保证询问的时候每次询问都是 O(1)。如果询问的 xx 被标记了,就输出 -1;反之,输出 nx_x

还要注意一点,考场上一定要用上读入优化和输出优化,小心可能会被卡。

代码非常的好写,看看就可以了:

#include <bits/stdc++.h>
using namespace std;
inline int read()//读入优化
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        f = ch != '-';
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return f ? x : -x;
}
inline void write(int x)//输出优化
{
    if (x >= 10)
        write(x / 10);
    putchar(x % 10 + 48);
}
const int N = 1e7 + 100;
int T, x, ls;
int f[N], nx[N];
bool check(int x)//判断是否含有数字7
{
    while (x)
    {
        if (x % 10 == 7)
            return 1;
        x /= 10;
    }
    return 0;
}
void init()//预处理部分
{
    for (int i = 1; i <= N - 10; i++)
    {
        if (f[i])//如果被标记过,就跳过
            continue;
        if (check(i))//如果含有数字7,标记其倍数
        {
            for (int j = i; j <= N - 10; j += i)
                f[j] = 1;
            continue;
        }
        nx[ls] = i;//记录i
        ls = i;//更新ls
    }
}
int main()
{
    init();//先预处理
    T = read();
    while (T--)
    {
        x = read();
        if (f[x])//被标记了输出-1,否则输出nx
            puts("-1");
        else
            write(nx[x]), putchar('\n');
    }
    return 0;
}

2.数列

        我就按照我考场上的思考过程讲吧。

考虑 DP。(计数类不是组合数就是 DP 呀)

我们定义一个四维的状态f_{i,j,k,q}表示已经填好了 i个数,考虑完了v_{1...j},此时 S二进制下有 k个 1,有 q个已经赋值了的 a_i取了 j,且 q≥1 时的权值总和。

我们发现这个状态不是很好转移,因为我们无法通过 q转移 k。问题就在于,如何计算 k 呢?

比如说我们现在取了 x 个 2^y,那么若 2^i \operatorname{\&} x>0 则等价于在二进制下 S 的 y+i位上有一个 11 的贡献,所以对 k 的贡献为 \operatorname{popcnt}(x)\operatorname{popcnt} (x) 表示 x 在二进制下 1 的位数。也就是说,我们如果单考虑 S 二进制下 1 的个数,取 x 个 2^i 等价于取 \lfloor \frac{x}{2^{j-i}}\rfloor 个 2^j。 也就是说,后几位的数对于前面可能会有影响,我们可以把状态改成这样:

四维的状态f_{i,j,k,q}表示已经填好了 i 个数,考虑完了 v_{1...j},此时 S 二进制下有 k 个 1,若不考虑 j以后的数,等价于取了 q 个 j,且至少有一个是本身就取了 j

这个就很好转移了。我们有转移方程:

f_{i,j,k,q}=\sum\limits_{x=0}^{i} (C_i^x\cdot \sum\limits_{p=0}^{j-1} \sum\limits_{\lfloor tmp/2^{i-p}\rfloor+x=q}f_{i-x,p,k-\operatorname{popcnt}(q)+\operatorname{popcnt}(q-x)},tmp)

如何理解这个方程呢? x 表示有多少个数本身就取了 j,p 是上一个本身被取的数字,由于我们要倒退上一个状态我们的 k 就会改变。我们少了 x 个数,不会对更低位上的 1 产生影响,因此 k'=k-\operatorname{popcnt}(q)+\operatorname{popcnt}(q-x)。而 tmp 就是 p 这一位等价的个数。

我们容易发现,这个代码复杂度大约为 O(n^5m^2)。但是跑不满。其实严重跑不过,只有 50pts。

我们考虑优化,我们重新定义一下状态,如下:

四维的状态

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值