第十二届蓝桥杯 2021年省赛真题 (C/C++ 大学A组) 第二场


解析移步对应 Java组 的题解

上课摸的鱼。

有无 J A \mathrm{JA} JA 的题单,有点难找。


#A 双阶乘

本题总分: 5 5 5


问题描述

  一个正整数的双阶乘,表示不超过这个正整数且与它有相同奇偶性的所有正整数乘积。 n n n 的双阶乘用 n ! ! n!! n!! 表示。
  例如:
   3 ! ! = 3 × 1 = 3 3!! = 3 × 1 = 3 3!!=3×1=3
   8 ! ! = 8 × 6 × 4 × 2 = 384 8!! = 8 × 6 × 4 × 2 = 384 8!!=8×6×4×2=384
   11 ! ! = 11 × 9 × 7 × 5 × 3 × 1 = 10395 11!! = 11 × 9 × 7 × 5 × 3 × 1 = 10395 11!!=11×9×7×5×3×1=10395
  请问, 2021 ! ! 2021!! 2021!! 的最后 5 5 5 位(这里指十进制位)是多少?
  注意: 2021 ! ! = 2021 × 2019 × ⋅ ⋅ ⋅ × 5 × 3 × 1 2021!! = 2021 × 2019 × · · · × 5 × 3 × 1 2021!!=2021×2019××5×3×1
  提示:建议使用计算机编程解决问题。


答案提交

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


59375


#include <stdio.h>

int ans = 1, n = 2021, p = 100000;

int main() {
    while (n > 0)
        ans = ans * n % p, n -= 2;
    printf("%05d", ans);
}

  粪。


#B 格点

本题总分: 5 5 5


问题描述

  如果一个点 ( x , y ) (x, y) (x,y) 的两维坐标都是整数,即 x ∈ Z x ∈ Z xZ y ∈ Z y ∈ Z yZ,则称这个点为一个格点。
  如果一个点 ( x , y ) (x, y) (x,y) 的两维坐标都是正数,即 x > 0 x > 0 x>0 y > 0 y > 0 y>0,则称这个点在第一象限。
  请问在第一象限的格点中,有多少个点 ( x , y ) (x, y) (x,y) 的两维坐标乘积不超过 2021 2021 2021,即 x ⋅ y ≤ 2021 x · y ≤ 2021 xy2021
  提示:建议使用计算机编程解决问题。


答案提交

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


15698


朴素解法


#include <stdio.h>

int ans = 0, n = 2021;

int main() {
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i * j <= n) ans++;
    printf("%d", ans);
}

倍数法


  这道题与统计不大于 2021 2021 2021 的自然数中的每个数的因数个数等价。

  因为对于任意不同的自然数 n = x × y n = x × y n=x×y,生成其的二元组 ( x , y ) (x,y) (x,y) 互相独立,于是问题等价于上述命题。

  这时我们选用倍数法去统计因数个数之和,即可在 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的复杂度下计算出答案,这是因为不大于 n n n 的自然数的因数个数之和约等于 n log ⁡ n n \log n nlogn 个,而倍数法对于每个因数只统计一次。

#include <stdio.h>

int ans = 0, n = 2021;

int main() {
    for (int i = 1; i <= n; i++)
        for (int j = 1; i * j <= n; j++) ans++;
    printf("%d", ans);
}

#C 整数分解

本题总分: 10 10 10


问题描述

  将 3 3 3 分解成两个正整数的和,有两种分解方法,分别是 3 = 1 + 2 3 = 1 + 2 3=1+2 3 = 2 + 1 3 = 2 + 1 3=2+1。注意顺序不同算不同的方法。
  将 5 5 5 分解成三个正整数的和,有 6 6 6 种分解方法,它们是 1 + 1 + 3 = 1 + 2 + 2 = 1 + 3 + 1 = 2 + 1 + 2 = 2 + 2 + 1 = 3 + 1 + 1 1+1+3 = 1+2+2 = 1 + 3 + 1 = 2 + 1 + 2 = 2 + 2 + 1 = 3 + 1 + 1 1+1+3=1+2+2=1+3+1=2+1+2=2+2+1=3+1+1
  请问,将 2021 2021 2021 分解成五个正整数的和,有多少种分解方法?


答案提交

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


691677274345


归纳法


  对于 n n n n ∈ Z + n \in \mathbb{Z^+} nZ+,设 n n n 划分 n ′ n' n 个的方案数为 f n , n ′ f_{n,n'} fn,n

  划分为 1 1 1 个正整数的方案为一,即 n n n

  划分为 2 2 2 个正整数的方案,有 n − 1 n - 1 n1 种,即 { n − k , k } \{n-k,k\} {nk,k} k ∈ Z + k \in \mathbb{Z^+} kZ+ n ≥ 2 n \geq 2 n2

  划分为 3 3 3 个正整数的方案,有 ( n − 1 ) ( n − 2 ) 2 \cfrac{(n-1)(n-2)}{2} 2(n1)(n2) 种,即 ∑ i = 2 n − 1 ( f i , 2 × f n − i , 1 ) = ∑ i = 2 n − 1 ( i − 1 ) \displaystyle\sum_{i = 2}^{n-1} (f_{i,2} ×f_{n-i,1}) = \displaystyle\sum_{i = 2}^{n-1} (i - 1) i=2n1(fi,2×fni,1)=i=2n1(i1) n ≥ 3 n \geq 3 n3

  划分为 5 5 5 个正整数的方案,有 ∑ i = 3 n − 2 ( f i , 3 × f n − i , 2 ) = ∑ i = 3 n − 2 ( i − 1 ) ( i − 2 ) ( n − i − 1 ) 2 = n 2 ∑ i = 3 n − 2 { ( i − 1 ) ( i − 2 ) } − 1 2 ∑ i = 3 n − 2 { ( i 2 − 1 ) ( i − 2 ) } = n 2 ( ( n − 3 ) ( n − 4 ) ( 2 n − 7 ) 6 + ( n + 1 ) ( n − 4 ) 2 − 2 ( n − 4 ) ) − 1 2 ( ( ( n − 2 ) ( n − 3 ) 2 ) 2 + ( n − 1 ) ( n − 2 ) ( 2 n − 3 ) 6 − ( 2 n − 1 ) ( n − 4 ) − 6 ) \displaystyle\sum_{i = 3}^{n-2} (f_{i,3} ×f_{n-i,2}) = \displaystyle\sum_{i=3}^{n-2} \cfrac{(i-1)(i-2)(n-i-1)}{2} = \frac n2\displaystyle\sum_{i=3}^{n-2}\{(i-1)(i-2)\} - \frac12\displaystyle\sum_{i=3}^{n-2}\{(i^2-1)(i-2)\} = \frac n2\left(\cfrac{(n-3)(n-4)(2n-7)}{6} + \cfrac{(n+1)(n-4)}{2} - 2(n-4)\right) - \frac 12\left((\cfrac{(n-2)(n-3)}2)^2+\cfrac{(n-1)(n-2)(2n-3)}{6}-(2n - 1)(n-4)-6\right) i=3n2(fi,3×fni,2)=i=3n22(i1)(i2)(ni1)=2ni=3n2{(i1)(i2)}21i=3n2{(i21)(i2)}=2n(6(n3)(n4)(2n7)+2(n+1)(n4)2(n4))21((2(n2)(n3))2+6(n1)(n2)(2n3)(2n1)(n4)6) n ≥ 5 n \geq 5 n5

  其实是等于 C 2020 4 C_{2020}^{4} C20204 的,但整理太费事了。

  而且整理通项公式也容易出错,

  我的建议是直接暴力累加。

#include <stdio.h>

long long ans = 0, n = 2021;

int main() {
    for (int i = 3; i <= n - 2; i++)
        ans += (i - 1) * (i - 2) * (n - i - 1) / 2;
    printf("%lld", ans);
}

数理分析


  如果将 n n n 看做 n n n 1 1 1 累加的结果,即 2021 = 1 + 1 + ⋯ + 1 ⏟ 2021 2021 = \begin{matrix} \underbrace{ 1+1+\cdots+1 } \\ 2021\end{matrix} 2021= 1+1++12021,将每个 + + + 视作一个间隙,则间隙共有 2020 2020 2020 个,对于每一种划分,我们都可以表示成任选四个间隙,然后先对间隙外的 1 1 1 求和,

  比如:

   15 = 1 + 1 + 1 + 1 + 1   +   1 + 1 + 1 + 1   +   1 + 1 + 1   +   1 + 1   +   1 = ( 1 + 1 + 1 + 1 + 1 ) + ( 1 + 1 + 1 + 1 ) + ( 1 + 1 + 1 ) + ( 1 + 1 ) + 1 = 5 + 4 + 3 + 2 + 1 \begin{aligned} 15&=1+1+1+1+1{\color{red}\:+\:}1+1+1+1{\color{red}\:+\:}1+1+1{\color{red}\:+\:}1+1{\color{red}\:+\:}1\\ &=(1+1+1+1+1){\color{red}+}(1+1+1+1){\color{red}+}(1+1+1){\color{red}+}(1+1){\color{red}+}1\\ &= 5 + 4 + 3 + 2 + 1 \end{aligned} 15=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1=(1+1+1+1+1)+(1+1+1+1)+(1+1+1)+(1+1)+1=5+4+3+2+1

   n = 2021 n = 2021 n=2021 时,这样的划分有 C 2021 − 1 4 C_{2021-1}^4 C202114 个,

  不能说跟概率毫不相干,

  所以标题起了这个。

#include <stdio.h>

long long C(int n, int m) {
    long long C = 1;
    for (int i = 0; i < m; i++)
        C = C * (n - i) / (i + 1);
    return C;
}

int main() {
    printf("%lld", C(2021 - 1, 4));
}

#D 城邦

本题总分: 10 10 10


问题描述

  小蓝国是一个水上王国,有 2021 2021 2021 个城邦,依次编号 1 1 1 2021 2021 2021。在任意两个城邦之间,都有一座桥直接连接。
  为了庆祝小蓝国的传统节日,小蓝国政府准备将一部分桥装饰起来。对于编号为 a a a b b b 的两个城邦,它们之间的桥如果要装饰起来,需要的费用如下计算:找到 a a a b b b 在十进制下所有不同的数位,将数位上的数字求和。
  例如,编号为 2021 2021 2021 922 922 922 两个城邦之间,千位、百位和个位都不同,将这些数位上的数字加起来是 ( 2 + 0 + 1 ) + ( 0 + 9 + 2 ) = 14 (2 + 0 + 1) + (0 + 9 + 2) = 14 (2+0+1)+(0+9+2)=14。注意 922 922 922 没有千位,千位看成 0 0 0
  为了节约开支,小蓝国政府准备只装饰 2020 2020 2020 座桥,并且要保证从任意一个城邦到任意另一个城邦之间可以完全只通过装饰的桥到达。
  请问,小蓝国政府至少要花多少费用才能完成装饰。
  提示:建议使用计算机编程解决问题。


答案提交

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


4046


#include <stdio.h>
#include <string.h>

const int n = 2021;

int d[n + 1], v[n + 1], ans;

int min(int a, int b) { return a < b ? a : b; }

int calc(int a, int b) { return a == b ? 0 : a + b; }

int dist(int x, int y) { return calc(x / 1000, y / 1000) + calc(x / 100 % 10, y / 100 % 10) + calc(x / 10 % 10, y / 10 % 10) + calc(x % 10, y % 10); }

int main() {
    memset(d, 0x3F, sizeof d);
    d[1] = 0;
    for (int i = 1; i < n; i++) {
        int x = 0;
        for (int j = 1; j <= n; j++)
            if (!v[j] && (!x || d[j] < d[x])) x = j;
            v[x] = 1;
        for (int y = 1; y <= n; y++)
            if (!v[y]) d[y] = min(d[y], dist(x, y));
    }
    for (int i = 1; i <= n; i++) ans += d[i];
    printf("%d", ans);
}

  稠密图用 P r i m \mathrm{Prim} Prim


#E 游戏

本题总分: 15 15 15


问题描述

  小蓝闲着无聊开始自己和自己做游戏。
  首先规定一个正整数 n n n
  他首先在纸上写下一个 1 1 1 n n n 之间的数。在之后的每一步,小蓝都可以选择上次写的数的一个约数(不能选上一个写过的数),写在纸上。直到最终小蓝写下 1 1 1
  小蓝可能有多种游戏的方案。
  例如,当 n = 6 n = 6 n=6 时,小蓝有 9 9 9 种方案: ( 1 ) (1) (1), ( 2 , 1 ) (2, 1) (2,1), ( 3 , 1 ) (3, 1) (3,1), ( 4 , 1 ) (4, 1) (4,1), ( 4 , 2 , 1 ) (4, 2, 1) (4,2,1), ( 5 , 1 ) (5, 1) (5,1), ( 6 , 1 ) (6, 1) (6,1), ( 6 , 2 , 1 ) (6, 2, 1) (6,2,1), ( 6 , 3 , 1 ) (6, 3, 1) (6,3,1)
  请问,当 n = 20210509 n = 20210509 n=20210509 时有多少种方案?


答案提交

  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


1352184317599


#include <stdio.h>

const int n = 20210509;

long long ans, dp[n + 1] = {0, 1};

int main() {
    for (int i = 1; i <= n; ans += dp[i++])
        for (int j = i << 1; j <= n; j += i) dp[j] += dp[i];
    printf("%lld", ans);
}

  直接将答案与 n n n 关联,似乎难以找到一种合适的算法。

  于是设 f i f_i fi 为以 i i i 开始的游戏方案数,显然答案为 ∑ i = 1 n f i \sum_{i=1}^n f_{i} i=1nfi f 1 = 1 f_1 = 1 f1=1 f i = ∑ j , j ∣ i , j ≠ i f j f_i = \sum_{j,j \mid i,j \neq i} f_j fi=j,ji,j=ifj


#F 小平方

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 15 15 15


问题描述

  小蓝发现,对于一个正整数 n n n 和一个小于 n n n 的正整数 v v v,将 v v v 平方后对 n n n 取余可能小于 n n n 的一半,也可能大于等于 n n n 的一半。
  请问,在 1 1 1 n − 1 n − 1 n1 中,有多少个数平方后除以 n n n 的余数小于 n n n 的一半。
  例如,当 n = 4 n = 4 n=4 时, 1 , 2 , 3 1, 2, 3 1,2,3 的平方除以 4 4 4 的余数都小于 4 4 4 的一半。
  又如,当 n = 5 n = 5 n=5 时, 1 , 4 1, 4 1,4 的平方除以 5 5 5 的余数都是 1 1 1,小于 5 5 5 的一半。而 2 , 3 2, 3 2,3 的平方除以 5 5 5 的余数都是 4 4 4,大于等于 5 5 5 的一半。


输入格式

  输入一行包含一个整数 n n n


输出格式

  输出一个整数,表示满足条件的数的数量。


测试样例1

Input:
5

Output:
2

评测用例规模与约定

  对于所有评测用例, 1 ≤ n ≤ 10000 1 ≤ n ≤ 10000 1n10000


#include <stdio.h>

int n, ans;

int main() {
    scanf("%d", &n);
    for (int i = 1; i < n; ++i)
        if (i * i % n < n + 1 >> 1) ans++;
    printf("%d", ans);
}

  写了个 ∑ i = 1 n − 1 ⌊ 2 ( i 2 − n ⌊ i 2 n ⌋ ) n ⌋ \displaystyle\sum_{i=1}^{n-1}\left\lfloor\cfrac{2(i^2 - n\lfloor\frac {i^{_2}}n\rfloor)}n\right\rfloor i=1n1n2(i2nni2) 研究半天,毛都没研究出来。


#G 完全平方数

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20


问题描述

  一个整数 a a a 是一个完全平方数,是指它是某一个整数的平方,即存在一个整数 b b b,使得 a = b 2 a = b^{2} a=b2
  给定一个正整数 n n n,请找到最小的正整数 x x x,使得它们的乘积是一个完全平方数。


输入格式

  输入一行包含一个正整数 n n n


输出格式

  输出找到的最小的正整数 x x x


测试样例1

Input:
12

Output:
3

测试样例2

Input:
15

Output:
15

评测用例规模与约定

  对于 30 30 30% 的评测用例, 1 ≤ n ≤ 1000 1 ≤ n ≤ 1000 1n1000,答案不超过 1000 1000 1000
  对于 60 60 60% 的评测用例, 1 ≤ n ≤ 1 0 8 1 ≤ n ≤ 10^{8} 1n108,答案不超过 1 0 8 10^{8} 108
  对于所有评测用例, 1 ≤ n ≤ 1 0 12 1 ≤ n ≤ 10^{12} 1n1012,答案不超过 1 0 12 10^{12} 1012


#include <stdio.h>

long long n, m, x = 1;

int main() {
    scanf("%d", &n);
    for (int i = 2; i <= n; ++i) {
        for (m = 0; !(n % i); n /= i, m++);
        if (m & 1) x *= i;
    }
    printf("%lld", x);
}

  每届必考的算术基本定理。

  有 a = b 2 = ∏ p i 2 k i a = b^2 = \prod p^{2k_{i}}_{i} a=b2=pi2ki n x = ∏ p i k n i + k x i nx = \prod p^{k_{n_{i}} +k_{x_{i}}}_{i} nx=pikni+kxi

  则 x = ∏ p i k x i x = \prod p^{k_{x_{i}}}_{i} x=pikxi,满足 2 ∣ ( k n i + k x i ) 2 \mid (k_{n_{i}} +k_{x_{i}}) 2(kni+kxi),且 x i x_i xi 最小即可。


#H 负载均衡

时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20


问题描述

  有 n n n 台计算机,第 i i i 台计算机的运算能力为 v i v_{i} vi
  有一系列的任务被指派到各个计算机上,第 i i i 个任务在 a i a_{i} ai 时刻分配,指定计算机编号为 b i b_{i} bi ,耗时为 c i c_{i} ci 且算力消耗为 d i d_{i} di 。如果此任务成功分配,将立刻开始运行,期间持续占用 b i b_{i} bi 号计算机 d i d_{i} di 的算力,持续 c i c_{i} ci 秒。
  对于每次任务分配,如果计算机剩余的运算能力不足则输出 − 1 −1 1,并取消这次分配,否则输出分配完这个任务后这台计算机的剩余运算能力。


输入格式

  输入的第一行包含两个整数 n , m n, m n,m,分别表示计算机数目和要分配的任务数。
  第二行包含 n n n 个整数 v 1 , v 2 , ⋅ ⋅ ⋅ v n v_{1}, v_{2}, · · · v_{n} v1,v2,vn,分别表示每个计算机的运算能力。
  接下来 m m m 行每行 4 4 4 个整数 a i , b i , c i , d i a_{i}, b_{i}, c_{i}, d_{i} ai,bi,ci,di,意义如上所述。数据保证 a i a_{i} ai 严格递增,即 a i < a i + 1 a_{i} < a_{i+1} ai<ai+1


输出格式

  输出 m m m 行,每行包含一个数,对应每次任务分配的结果。


测试样例1

Input:
2 6
5 5
1 1 5 3
2 2 2 6
3 1 2 3
4 1 6 1
5 1 3 3
6 1 3 4

Output:
2
-1
-1
1
-1
0

Explanation:
时刻 1,第 1 个任务被分配到第 1 台计算机,耗时为 5 ,这个任务时刻 6 会结束,占用计算机 1 的算力 3。
时刻 2,第 2 个任务需要的算力不足,所以分配失败了。
时刻 3,第 1 个计算机仍然正在计算第 1 个任务,剩余算力不足 3,所以失败。
时刻 4,第 1 个计算机仍然正在计算第 1 个任务,但剩余算力足够,分配后剩余算力 1。
时刻 5,第 1 个计算机仍然正在计算第 1, 4 个任务,剩余算力不足 4,失败。
时刻 6,第 1 个计算机仍然正在计算第 4 个任务,剩余算力足够,且恰好用完。

评测用例规模与约定

  对于 20 20 20% 的评测用例, n , m ≤ 200 n, m ≤ 200 n,m200
  对于 40 40 40% 的评测用例, n , m ≤ 2000 n, m ≤ 2000 n,m2000
  对于所有评测用例, 1 ≤ n , m ≤ 200000 1 ≤ n, m ≤ 200000 1n,m200000 1 ≤ a i , c i , d i , v i ≤ 1 0 9 1 ≤ a_{i}, c_{i}, d_{i}, v_{i} ≤ 10^{9} 1ai,ci,di,vi109 1 ≤ b i ≤ n 1 ≤ b_{i} ≤ n 1bin


#include <bits/stdc++.h>

using namespace std;

int V[200001];

priority_queue<pair<int, pair<int, int>>> pq;

int main() {
    int n, m, a, b, c, d;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a), V[i] = a;
    pq.push(make_pair(-1000000009, make_pair(0, 0)));
    while (~scanf("%d %d %d %d", &a, &b, &c, &d)) {
        while (abs(pq.top().first) <= a)
            V[pq.top().second.first] += pq.top().second.second, pq.pop();
        if (V[b] >= d) {
            pq.push(make_pair(-a - c, make_pair(b, d)));
            printf("%d\n", V[b] -= d);
        } else printf("-1\n");
    }
}

  优先列队模拟。


#I 国际象棋

时间限制: 1.0 1.0 1.0s 内存限制: 256.0 256.0 256.0MB 本题总分: 25 25 25


问题描述

  众所周知,“八皇后” 问题是求解在国际象棋棋盘上摆放 8 8 8 个皇后,使得两两之间互不攻击的方案数。已经学习了很多算法的小蓝觉得 “八皇后” 问题太简单了,意犹未尽。作为一个国际象棋迷,他想研究在 N × M N × M N×M 的棋盘上,摆放 K K K 个马,使得两两之间互不攻击有多少种摆放方案。由于方案数可能很大,只需计算答案除以 1000000007 1000000007 1000000007 (即 1 0 9 + 7 10^{9} + 7 109+7) 的余数。
  如下图所示,国际象棋中的马摆放在棋盘的方格内,走 “日” 字,位于 ( x , y ) (x, y) (x,y) 格的马(第 x x x 行第 y y y 列)可以攻击 ( x + 1 , y + 2 ) (x + 1, y + 2) (x+1,y+2) ( x + 1 , y − 2 ) (x + 1, y − 2) (x+1,y2) ( x − 1 , y + 2 ) (x − 1, y + 2) (x1,y+2) ( x − 1 , y − 2 ) (x − 1, y − 2) (x1,y2) ( x + 2 , y + 1 ) (x + 2, y + 1) (x+2,y+1) ( x + 2 , y − 1 ) (x + 2, y − 1) (x+2,y1) ( x − 2 , y + 1 ) (x − 2, y + 1) (x2,y+1) ( x − 2 , y − 1 ) (x − 2, y − 1) (x2,y1) 8 8 8 个格子。

请添加图片描述


输入格式

  输入一行包含三个正整数 N , M , K N, M, K N,M,K,分别表示棋盘的行数、列数和马的个数。


输出格式

  输出一个整数,表示摆放的方案数除以 1000000007 1000000007 1000000007 (即 1 0 9 + 7 10^{9} + 7 109+7) 的余数。


测试样例1

Input:
1 2 1

Output:
2

测试样例2

Input:
4 4 3

Output:
276

测试样例3

Input:
3 20 12

Output:
914051446

评测用例规模与约定

  对于 5 5 5% 的评测用例, K = 1 K = 1 K=1
  对于另外 10 10 10% 的评测用例, K = 2 K = 2 K=2
  对于另外 10 10 10% 的评测用例, N = 1 N = 1 N=1
  对于另外 20 20 20% 的评测用例, N , M ≤ 6 , K ≤ 5 N, M ≤ 6,K ≤ 5 N,M6K5
  对于另外 25 25 25% 的评测用例, N ≤ 3 , M ≤ 20 , K ≤ 12 N ≤ 3,M ≤ 20,K ≤ 12 N3M20K12
  对于所有评测用例, 1 ≤ N ≤ 6 1 ≤ N ≤ 6 1N6 1 ≤ M ≤ 100 1 ≤ M ≤ 100 1M100 1 ≤ K ≤ 20 1 ≤ K ≤ 20 1K20


状压 DP


  一个摆放方案是否合法,取决于旗子之间是否有相互攻击的距离,旗子数量,于是可以按列拆分,有状态 f i , k , s i , s i − 1 f_{i,k,s_i,s_{i-1}} fi,k,si,si1,表示已经摆放 1 1 1 i i i 列,其中第 i i i 列的摆放方案为 s i s_{i} si,第 i − 1 i - 1 i1 列的摆放方案为 s i − 1 s_{i-1} si1,且恰摆放了 k k k 个旗子时,合法的方案种数模 1 e 9 + 7 1e9+7 1e9+7 的值。

  其中 s i s_i si 为一串 n n n 位二进制整数,第 j j j 1 1 1 表示在棋盘第 j j j i i i 列摆有一个马,为 0 0 0 则恰反。

  于是有状态转移方程 f i , k , n o w , p r e 1 = ∑ f i − 1 , k − k ′ , p r e 1 , p r e 2 f_{i,k,now,pre1} = \displaystyle\sum f_{i-1,k-k',pre1,pre2} fi,k,now,pre1=fi1,kk,pre1,pre2   n o w now now p r e 1 pre1 pre1 p r e 2 pre2 pre2 两两不冲突, k ′ k' k n o w now now 中二进制位为 1 1 1 的位的总数。

   n o w now now p r e 1 pre1 pre1 不冲突可表示为 n o w   &   ( p r e 1 < < 2 ) now\ \&\ (pre1 << 2) now & (pre1<<2) n o w   &   ( p r e 1 > > 2 ) now\ \&\ (pre1 >> 2) now & (pre1>>2) 的结果同为 1 1 1 ,即对于 p r e 1 pre1 pre1 上的每一个马,能攻击到 n o w now now 上的马,只在某两只马距离的绝对差为 2 2 2 时,值得注意的是, p r e 1 > > 2 pre1 >> 2 pre1>>2 可能会丢失 p r e 1 pre1 pre1 的信息,于是换为判断 ( n o w < < 2 )   &   p r e 1 (now << 2)\ \&\ pre1 (now<<2) & pre1

   n o w now now p r e 2 pre2 pre2 同上,若 p r e 1 pre1 pre1 p r e 2 pre2 pre2 冲突,对于任意 i i i k k k f i , k , p r e 1 , p r e 2 f_{i,k,pre1,pre2} fi,k,pre1,pre2 0 0 0,因此可以不用判断。

#include <stdio.h>

const int N = 6, M = 101, K = 20;

int n, m, k, dp[M][K][1 << N][1 << N] = {1}, ans, p = 1000000007;

int bitCount(int n) {
    n = n - (n >> 1 & 0x55555555);
    n = (n & 0x33333333) + (n >> 2 & 0x33333333);
    n = n + (n >> 4) & 0x0F0F0F0F;
    n = n + (n >> 8);
    n = n + (n >> 16);
    return n & 0x3F;
}

int main() {
    scanf("%d %d %d", &n, &m, &k);
    for (int i = 1; i <= m; ++i)
        for (int now = 0; now < 1 << n; ++now)
            for (int kk = bitCount(now); kk <= k; ++kk)
                for (int pre1 = 0; pre1 < 1 << n; ++pre1)
                    if (!(now << 2 & pre1) && !(pre1 << 2 & now))
                        for (int pre2 = 0; pre2 < 1 << n; ++pre2)
                            if (!(now << 1 & pre2) && !(pre2 << 1 & now))
                                dp[i][kk][now][pre1] = (dp[i][kk][now][pre1] + dp[i - 1][kk - bitCount(now)][pre1][pre2]) % p;
    for (int now = 0; now < 1 << n; ++now)
        for (int pre = 0; pre < 1 << n; ++pre)
            ans = (ans + dp[m][k][now][pre]) % p;
    printf("%d", ans);
}

#J 完美序列

时间限制: 2.0 2.0 2.0s 内存限制: 512.0 512.0 512.0MB 本题总分: 25 25 25


问题描述

  一个序列中取出一些元素按照原来的顺序排列成新的序列称为该序列的一个子序列。子序列的价值为子序列中所有元素的和。
  如果一个序列是单调递减的,而且除了第一个数以外的任何一个数都是上一个数的因数,则称这个序列为一个完美序列。
  一个序列中的一个子序列如果是完美序列,则称为该序列的一个完美子序列。一个序列的最长完美子序列长度,称为该序列的完美长度。
  给定正整数 n n n 1 1 1 n n n 的所有排列的完美长度的最大值,称为 n n n 阶最大完美长度。
  给定正整数 n n n,请求出 1 1 1 n n n 的所有排列中长度正好为 n n n 阶最大完美长度的所有完美子序列的价值的和。


输入格式

  每个评测用例包含多组询问。询问之间彼此独立。
  输入的第一行包含一个整数 T T T,表示询问数。
  接下来 T T T 行,每行包含一个整数 n n n,表示一个给定的 n n n


输出格式

  输出 T T T 行,依次对应每组询问的答案。
  每行包含一个整数,表示对应的答案除以 1000000007 1000000007 1000000007 (即 1 0 9 + 7 10^9 + 7 109+7) 的余数。


测试样例1

Input:
5
1
2
3
5
10

Output:
1
3
21
140
2268000

Explanation:
当 n = 1 时,答案显然是 1。
当 n = 2 时,全排列包括 (1, 2) 和 (2, 1),其中 (2, 1) 拥有最长的完美子序列,也就是 (2, 1) 本身,2 阶最大完美长度为 2,答案即为 2 + 1。
当 n = 3 时,全排列包括 (1, 2, 3)、(1, 3, 2)、(2, 1, 3)、(2, 3, 1)、(3, 1, 2)、(3, 2, 1)。其中 (2, 1) 和 (3, 1) 都是最长的完美子序列,3 阶最大完美长度为 2。
序列 (1, 2, 3) 和 (1, 3, 2) 中没有长度为 2 的完美子序列。
序列 (2, 1, 3) 中有完美子序列 (2, 1),价值和为 3。
序列 (2, 3, 1) 中有完美子序列 (2, 1) 和 (3, 1),价值和为 7。
序列 (3, 1, 2) 中有完美子序列 (3, 1),价值和为 4。
序列 (3, 2, 1) 中有完美子序列 (2, 1) 和 (3, 1),价值和为 7。
答案为 3 + 7 + 4 + 7 = 21。

评测用例规模与约定

  对于 10 10 10% 的评测用例, n ≤ 10 n ≤ 10 n10
  对于 20 20 20% 的评测用例, n ≤ 20 n ≤ 20 n20
  对于 30 30 30% 的评测用例, T ≤ 20 T ≤ 20 T20 n ≤ 1000 n ≤ 1000 n1000
  对于 40 40 40% 的评测用例, T ≤ 20 T ≤ 20 T20 n ≤ 1 0 5 n ≤ 10^5 n105
  对于所有评测用例, 1 ≤ T ≤ 1 0 5 1 ≤ T ≤ 10^5 1T105 1 ≤ n ≤ 1 0 6 1 ≤ n ≤ 10^6 1n106


动态规划


   T T T n n n 1 e 5 1e5 1e5 1 e 6 1e6 1e6 这个量级, O ( n T ) O(nT) O(nT) 显然无法通过所有测试点,于是考虑动态规划。

  设 f i = { r a n k , s u m , s i z e } f_{i} = \{rank,sum,size\} fi={rank,sum,size},其中 r a n k rank rank i i i 为首的完美子序列的长度, s i z e size size 表示其个数, s u m sum sum 表示其的价值的和。

  对于每一个询问 n n n,都可以转换为 f f f 上的求和,即 A n n − max ⁡ ( f . r a n k ) ∑ i f i . s u m A_{n}^{n-\max(f.rank)} \displaystyle\sum_{i} f_{i}.sum Annmax(f.rank)ifi.sum i i i 满足 f i = max ⁡ ( f . r a n k ) f_i = \max (f.rank) fi=max(f.rank),即统计长度恰为 n n n 阶最大完美长度的所有完美子序列的价值的和,乘以 ( ( ( n n n, n n n n n n 阶最大完美长度 ) ) ) 的排列数。

  有转移 f i = { k + 1 , s × i + ∑ f j . s u m , s } f_{i} = \{k + 1,s×i + \sum f_{j}.sum,s\} fi={k+1,s×i+fj.sum,s},其中 k k k f j . r a n k f_j.rank fj.rank j < i j < i j<i 中的最大值, s s s ∑ j ∣ f j . r a n k = k f j . s i z e \sum_{j \mid f_j.rank = k} f_{j}.size jfj.rank=kfj.size

  然后求一下 ( 1 , n ! ) (1,n!) (1,n!) 的逆元,添加一点细节就大功告成了;

#include <stdio.h>

const int N = 1e6 + 1;

int T, n, dp[N + 1][3] = { 0, 0, 0, 1, 1, 1 }, sum[N + 1], start[N + 1], p = 1000000007;

long long pro[N + 1] = {0, 1}, inv[N + 1] = {0, 1}, buf[N + 1] = {0, 1};

int main() {
    for (int i = 1; i <= N; i++) {
        start[i] = dp[i][0] > dp[i - 1][0] ? i : start[i - 1];
        sum[i] = (sum[i - 1] + (dp[start[i]][0] == dp[i][0] ? dp[i][1] : 0)) % p;
        for (int j = i << 1; j <= N; j += i) {
            if (dp[i][0] + 1 == dp[j][0]) {
                dp[j][1] = (dp[j][1] + j + dp[i][1]) % p;
                dp[j][2]++;
            }
            else if (dp[i][0] >= dp[j][0]) {
                dp[j][1] = ((long long)j * dp[i][2] % p + dp[i][1]) % p;
                dp[j][0] = dp[i][0] + 1;
                dp[j][2] = dp[i][2];
            }
        }
    }
    for (int i = 2; i <= N; i++)
        pro[i] = pro[i - 1] * i % p,
        buf[i] = (p - p / i) * buf[p % i] % p,
        inv[i] = inv[i - 1] * buf[i] % p;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        printf("%d\n", (sum[n] - sum[start[n] - 1] + p) % p * (pro[n] * inv[dp[start[n]][0]]) % p);
    }
}

  我的评价是,

  不想出题可以不出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值