第十二届蓝桥杯 2021年省赛真题 (Java 大学B组) 第二场


  第一场最后一题做自闭了,换场子了


#A 求余

本题总分: 5 5 5


问题描述

  在 C / C + + / J a v a / P y t h o n \mathrm{C/C++/Java/Python} C/C++/Java/Python 等语言中,使用 % \mathrm{\%} % 表示求余,请问 2021 % 20 \mathrm{2021\%20} 2021%20 的值是多少?


答案提交

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


1

  唉


#B 双阶乘

本题总分: 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


calcCode

public class Test {

    public static void main(String[] args) { new Test().run(); }

    void run() {
        int n = 2021 + 2, ans = 1;
        while (n > 1)
            ans = (ans * (n -= 2)) % 100000;
        System.out.println(ans);
    }
}

   ( a × b )   % p = ( a   %   p × b   %   p )   % p (a × b)\ \% p = (a\ \%\ p × b\ \%\ p)\ \% p (a×b) %p=(a % p×b % p) %p p ∈ Z p \in \mathbb{Z} pZ

  没啥好讲的。


#C 格点

本题总分: 10 10 10


问题描述

  如果一个点 ( 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


朴素枚举


public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021;

    void run() {
        int ans = 0;
        for (int x = 1; x <= n; x++)
            for (int y = 1; y <= n; y++)
                  if (x * y <= n) ans++;
        System.out.println(ans);
    }
}

  不谈。


稍微优化


  题目解读一下就是多少个正整数二元组的乘积等于 k k k k ∈ [ 1 , 2021 ] k \in [1,2021] k[1,2021],我们可以用欧拉筛线性的分解出区间内每个数的质因数,用其质数计算出组合方案的种数。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021, ans = 0;

    void run() {
        Map<Integer, Integer>[] nums = new Map[n + 1];
        nums[1] = new HashMap(){{ put(0, 0); }};
        List<Integer> primes = new ArrayList();
        for (int i = 2; i <= n; i++) {
            if (nums[i] == null) {
               (nums[i] = new HashMap()).put(i, 1);
                primes.add(i);
            }
            for (int p : primes) {
                if (i * p > n) break;
                nums[i * p] = new HashMap(nums[i]);
                nums[i * p].put(p, nums[i].getOrDefault(p, 0) + 1);
                if (i % p == 0)break;
            }
        }
        for (int i = 1; i <= n; i++) {
            int count = 1;
            for (int k : nums[i].values())
                count *= k + 1;
            ans += count;
        }
        System.out.println(ans);
    }
}

  要是以后出个题,计算 20222222 的格点,记得给我打钱。


#D 整数分解

本题总分: 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


排列组合


   O ( n 5 ) O(n^{5}) O(n5) 指定是不行了,不过这里可以将算式分解成 k + ( a 1 + a 2 ) + ( b 1 + b 2 ) k + (a_1 + a_2) + (b_1 + b_2) k+(a1+a2)+(b1+b2) 三部分, 然后枚举每一个 k k k,暴力的求出 ( a 1 + a 2 ) (a_1 + a_2) (a1+a2) ( b 1 + b 2 ) (b_1 + b_2) (b1+b2) 可能的组合数,并将它们累加。

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021;

    void run() {
        long ans = 0;
        for (int k = 1, a, b; k <= n - 2 - 2; k++) {
            for (a = 2, b = n - 2 - k; b >= 2; a++, b--)
                ans += (a - 1) * (b - 1);
        }
        System.out.println(ans);
    }
}

  当然也可以推一下组合公式,

  有问题,将 N N N 分解成 k k k 个无序的正整数之和, N ≥ k N \ge k Nk,求方案数 A ( N , k ) A(N,k) A(N,k)

  显然 k = 2 k = 2 k=2 时, A ( N , 2 ) = N − 1 A(N,2)=N-1 A(N,2)=N1;

   k = 3 k = 3 k=3 时,显然 A ( N , 3 ) = A ( N − 1 , 2 ) + A ( N − 2 , 2 ) + ⋯ + A ( 2 , 2 ) = ( N − 1 ) ( N − 2 ) 2 A(N,3)=A(N-1,2) + A(N-2,2) + \cdots + A(2,2) = \cfrac{(N-1)(N-2)}{2} A(N,3)=A(N1,2)+A(N2,2)++A(2,2)=2(N1)(N2);

   k = 5 k = 5 k=5 时,我们整理一下 ∑ i = 2 N − 3 A ( i , 2 ) A ( N − i , 3 ) = ∑ i = 2 N − 3 i 3 + ( 2 − 2 N ) i 2 + ( N 2 − N − 1 ) i − N 2 + 3 N − 2 2 \displaystyle\sum_{i = 2}^{N-3}A(i,2)A(N-i,3) = \displaystyle\sum_{i = 2}^{N-3}\cfrac{i^3+(2−2N)i^2+(N^2−N−1)i−N^2+3N−2}{2} i=2N3A(i,2)A(Ni,3)=i=2N32i3+(22N)i2+(N2N1)iN2+3N2,又臭又长的一堆就不写了,总之等于 ( N − 1 ) ( N − 2 ) ( N − 3 ) ( N − 4 ) 4 × 3 × 2 \cfrac{(N-1)(N-2)(N-3)(N-4)}{4 × 3 × 2} 4×3×2(N1)(N2)(N3)(N4)

public class Test {

    public static void main(String[] args) { new Test().run(); }

    long n = 2021;

    void run() {
        System.out.println((n - 1) * (n - 2) * (n - 3) * (n - 4) / 4 / 3 / 2);
    }
}

  只会用笨方法,没上过高中体谅一下。


动态规划


  设有状态 d p ( k , i ) dp(k,i) dp(k,i) i i i 分解成 k k k 个正整数的方案数。

  在上述解法里,容易得出 A ( N , 3 ) = A ( N − 1 , 3 − 1 ) + A ( N − 1 , 3 ) A(N,3)=A(N-1,3-1) + A(N-1,3) A(N,3)=A(N1,31)+A(N1,3),具体的细节留给读者自行思考,这里直接给出状态转移方程: d p ( k , i ) = { 1 k = 1 d p ( k − 1 , i − 1 ) + d p ( k , i − 1 ) dp(k,i) = \left\{\begin{array}{lr}1&k=1\\dp(k-1,i-1) + dp(k,i-1)&\end{array}\right. dp(k,i)={1dp(k1,i1)+dp(k,i1)k=1

import java.util.Arrays;

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021, k = 5;

    void run() {
        long[][] dp = new long[k + 1][n + 1];
        Arrays.fill(dp[1], 1);
        for (int i = 2; i <= k; i++)
            for (int j = i; j <= n; j++)
                dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
        System.out.println(dp[k][n]);
    }
}

组合数


  两种方法无不在暗示结果等于 C 2020 4 C_{2020}^{4} C20204

  开个坑,不一定填。


#E 城邦

本题总分: 15 15 15


问题描述

  小蓝国是一个水上王国,有 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


最小树生成


  送分。

  具体地说就是:完全图上的最小树生成。

  也就是说该图具有 m = n ( n − 1 ) 2 m=\cfrac{n(n-1)}{2} m=2n(n1),在这里是一个超过 2 E 6 2E6 2E6 的数字。

  这里使用 K r u s k a l   O ( m log ⁡ m ) \mathrm{Kruskal}\ O(m \log m) Kruskal O(mlogm) 的去求解,不如朴素的做 P r i m \mathrm{Prim} Prim,它的复杂度在 O ( n 2 ) O(n^2) O(n2)

  通常,使用优先队列可以简单将 P r i m \mathrm{Prim} Prim 优化至 O ( m log ⁡ m ) O(m\log m) O(mlogm),但在 完全图 上,

  不如不做。

  下面给大🔥打两个板子。


Kruskal


import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021;

    int[] link = new int[n + 1];

    void run() {
        List<Edge> edges = new ArrayList();
        for (int v = 1; v < n; v++) {
            link[v] = v;
            for (int w = v + 1; w <= n; w++)
                edges.add(new Edge(v, w, calc(v, w)));
        }
        edges.sort((e1, e2)->(e1.weight - e2.weight));
        int ans = 0, k = 1;
        for (Edge edge : edges) {
            int v = find(edge.v);
            int w = find(edge.w);
            if (v != w) {
                link[v] = w;
                ans += edge.weight;
                if (++k == n) break;
            }
        }
        System.out.println(ans);
    }

    int find(int k) { return k == link[k] ? k : (link[k] = find(link[k])); }

    int calc(int v, int w) {
        int res = 0;
        for (int i = 0; i < 4; i++) {
            if (v % 10 != w %10)
                res += v % 10 + w % 10;
            v /= 10; w /= 10;
        }
        return res;
    }

    class Edge {

        int v, w, weight;

        Edge(int v, int w, int weight) {
            this.v = v;
            this.w = w;
            this.weight = weight;
        }
    }
}

Prim


import java.util.Arrays;

public class Test {

    public static void main(String[] args) { new Test().run(); }

    int n = 2021;

    void run() {
        boolean[] marked = new boolean[n + 1];
        int[] dist = new int[n + 1];
        Arrays.fill(dist, 0x7FFFFFFF);
        int ans = 0;
        dist[1] = 0;
        for (int k = 0; k < n; k++) {
            int V = 0;
            for (int i = 1; i <= n; i++)
                if (!marked[i] && dist[i] < dist[V]) V = i;
            marked[V] = true;
            ans += dist[V];
            for (int i = 1; i <= n; i++)
                if (!marked[i])
                    dist[i] = min(dist[i], calc(i, V));
        }
        System.out.println(ans);
    }

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

    int calc(int v, int w) {
        int res = 0;
        for (int i = 0; i < 4; i++) {
            if (v % 10 != w %10)
                res += v % 10 + w % 10;
            v /= 10; w /= 10;
        }
        return res;
    }
}

贪心 + Trie


   K r u s k a l \mathrm{Kruskal} Kruskal 的策略是,每次连接权值最小,且两边没有连通的边,

  在题目给定的权值计算方法下,权值最小的边显然为后缀(方便计算)重叠最多的两个节点构成的边,

  好像行不通,开坑,一定不填。


#F 特殊年份

时间限制: 1.0 1.0 1.0s 内存限制: 512.0 512.0 512.0MB 本题总分: 15 15 15


问题描述

  今年是 2021 2021 2021 年, 2021 2021 2021 这个数字非常特殊,它的千位和十位相等,个位比百位大 1 1 1,我们称满足这样条件的年份为特殊年份。
  输入 5 5 5 个年份,请计算这里面有多少个特殊年份。


输入格式

  输入 5 5 5 行,每行一个 4 4 4 位十进制数(数值范围为 1000 1000 1000 9999 9999 9999),表示一个年份。


输出格式

  输出一个整数,表示输入的 5 5 5 个年份中有多少个特殊年份。


测试样例1

Input:
2019
2021
1920
2120
9899

Output:
2

Explanation:
2021 和 9899 是特殊年份,其它不是特殊年份。

朴素解法


import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        Scanner in = new Scanner(System.in);
        int ans = 0, k = 5;
        while (k-- > 0)
            if (check(in.nextInt())) ans++;
        System.out.println(ans);
    }

    boolean check(int year) { return (year / 1000 == (year % 100) / 10) && ((year % 10) - 1 == (year % 1000) / 100); }
}

  签到。


BCD码


  虽然是签到题,但总想玩点花样,

  固然我们可以用一串字符来表示一个十进制的数,但在多数情况下,空间和运行效率都不理想。

  如果在需要频繁按位处理十进制上的位的问题的时候(当然不是只这个问题),我们可以引入 B C D \mathrm{BCD} BCD ( B i n a r y − C o d e d   D e c i m a l ‎ ) (\mathrm{Binary-Coded\ Decimal}‎) (BinaryCoded Decimal)

  用四位二进制来表示一位十进制数,而非二的四次幂也就是十六进制数。

  用若干位运算来替代取余和除法。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        Scanner in = new Scanner(System.in);
        int ans = 0, k = 5;
        while (k-- > 0)
            if (check(in.nextInt(16))) ans++;
        System.out.println(ans);
    }

    boolean check(int year) { return (year >> 12 == ((year >> 4) & 0xf)) && ((year & 0xf) - 1 == ((year >> 8) & 0xf)); }
}

  虽然不是这里,但总有用得上的时候。


#G 小平方

时间限制: 1.0 1.0 1.0s 内存限制: 512.0 512.0 512.0MB 本题总分: 20 20 20


问题描述

  小蓝发现,对于一个正整数 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


朴素解法


  没什么花里胡哨,套两 f o r \mathrm{for} for 比什么都好使。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        int n = new Scanner(System.in).nextInt(), ans = 0;
        for (int i = 1; i < n; i++)
            if (i * i % n < n + 1 >> 1) ans++;
        System.out.println(ans);
    }
}

  双签到了,属于是。


找循环节


  这里仅提供一个思路。

  设当前完全平方数为 a 2 a^{2} a2,则往右自然相邻的完全平方数为 ( a + 1 ) 2 (a+1)^{2} (a+1)2,即 a 2 + 2 a + 1 a^2 + 2a + 1 a2+2a+1

  显然,这个增量为单调有序序列,

  而在模 m m m 意义下, ( a + b ) % m = ( a % m + b % m ) % m (a+b)\%m = (a\%m+b\%m)\%m (a+b)%m=(a%m+b%m)%m

  也就是我们可以将增量拆分再取模成两个绝对差为零或一的序列,对其建立其一个平衡树,对于后半段只需两次查找就能知道满足条件的数的个数,整个算法的复杂度具体在 n 2 log ⁡ n 2 \cfrac{n}{2}\log \cfrac{n}{2} 2nlog2n

  没有根本意义上的优化,

  所以程序我也懒得实现了。


#H 完全平方数

时间限制: 2.0 2.0 2.0s 内存限制: 512.0 512.0 512.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


简单数学


  一个完全平方数,它的质因子的指数总是个偶数。

  因此,对给定的 n n n 分解质因数,将指数为奇数的质因子累乘起来,得到的积就是我们要找的 x x x

  特别地,我们需要将枚举的范围缩小至 n \sqrt{n} n ,以规避 n n n 为极大质数的情况。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        long n = new Scanner(System.in).nextLong();
        long ans = 1;
        for (long k = 2; k * k <= n; k++)
            if (n % k == 0) {
                int exp = 0;
                while (n % k == 0) {
                    n /= k;
                    exp++;
                }
                if (exp % 2 == 1) ans *= k;
            }
        System.out.println(n == 1 ? ans : n * ans);
    }
}

#I 负载均衡

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


问题描述

  有 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},\ \cdots, 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


优先队列模拟


  以为是个 B i g   I m p l e m e n t a t i o n \mathrm{Big\ Implementation} Big Implementation,构思完后发现代码量还蛮少的。

  直接用优先队列来模拟每个计算机正在运行的任务,

  当当前计算机即将可能被分配任务时,清除队列中已经被执行完的任务,并归还算力即可。

  看到那种输入有各种各样的变量的题,基本都是模拟,

  没什么营养。

import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    void run() {
        InputReader in = new InputReader(System.in);
        PrintWriter out = new PrintWriter(System.out);
        int n = in.readInt(), m = in.readInt();
        Queue<Item>[] cpt = new Queue[n + 1];
        int[] cptd = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            cpt[i] = new PriorityQueue();
            cptd[i] = in.readInt();
        }
        while (m-- > 0) {
            int a = in.readInt();
            int b = in.readInt();
            int c = in.readInt();
            int d = in.readInt();
            while (cpt[b].size() > 0 && cpt[b].peek().c <= a)
                cptd[b] += cpt[b].poll().d;
            if (cptd[b] >= d) {
                cpt[b].offer(new Item(a + c, d));
                out.println(cptd[b] -= d);
            } else out.println("-1");
        }
        out.flush();
    }

    class Item implements Comparable<Item> {

        int c, d;

        Item(int c, int d) {
            this.c = c;
            this.d = d;
        }

        public int compareTo(Item item) { return this.c - item.c; }
    }

    class InputReader {

        BufferedReader reader;
        StringTokenizer token;

        InputReader(InputStream in) {
            this.reader = new BufferedReader(new InputStreamReader(in));
        }

        String read() {
            while (token == null || !token.hasMoreTokens()) {
                try {
                    token = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return token.nextToken();
        }

        int readInt() { return Integer.parseInt(read()); }
    }
}

#J 国际象棋

时间限制: 3.0 3.0 3.0s 内存限制: 512.0 512.0 512.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


  题目类型和 N N N 的范围已经是明牌状压 d p \mathrm{dp} dp 了。

  由于给定条件的特殊性,以行组织的状态,受前两行的影响,故在定义具体的状态时,需要保存前一行的信息以便能正确的转移。

  再就是使用二进制串来表示一行, 0 ∣ 1 0 \mid 1 01 表示当前位置是否有 🐴, N ≤ 6 N ≤ 6 N6,我们至多使用 100 × 2 6 + 1 100 × 2^{6 + 1} 100×26+1 个二进制串就能保存所有行及其前一行的信息。

  现在确定状态有 d p ( m , l 1 , l 2 , k ) \mathrm{dp(m,l1,l2,k)} dp(m,l1,l2,k),表示在第 m \mathrm{m} m 行,摆放 l 1 \mathrm{l1} l1 状态的 🐴,在第 m − 1 \mathrm{m - 1} m1 行,摆放 l 2 \mathrm{l2} l2 状态的 🐴 的情况下,一共有多少种摆法。

  显然 d p ( m , l 1 , l 2 , k ) \mathrm{dp(m,l1,l2,k)} dp(m,l1,l2,k) d p ( m − 1 , l 2 , l 2 ′ , k ′ ) \mathrm{dp(m - 1,l2,l2',k')} dp(m1,l2,l2,k) 中所有与状态 l 1 \mathrm{l1} l1 无冲突的方案数的总和,故有状态转移方程: d p ( m , l 1 , l 2 , k ) = s u m { d p ( m − 1 , l 2 , l ′ , k ′ ) } dp(m,l1,l2,k) = sum\{dp(m -1,l2,l',k')\} dp(m,l1,l2,k)=sum{dp(m1,l2,l,k)}  其中 l 1 l1 l1 << 2 2 2 l 2 l2 l2 l 2 l2 l2 << 2 2 2 l 1 l1 l1 的结果应为 0 0 0,对应 ( x + 2 , y + 1 ) (x + 2, y + 1) (x+2,y+1) ( x − 2 , y + 1 ) (x - 2, y + 1) (x2,y+1) 上没 🐴,

   l 2 l2 l2 << 1 1 1 l ′ l' l l ′ l' l << 1 1 1 l 2 l2 l2 的结果应为 0 0 0,对应 ( x + 1 , y + 2 ) (x + 1, y + 2) (x+1,y+2) ( x − 1 , y + 2 ) (x − 1, y + 2) (x1,y+2) 上没 🐴,

  当一匹 🐴 可以攻击另一匹 🐴 时,另一匹 🐴 也能攻击到这匹 🐴,因此剩下的四个点位没有判断的意义,

  当严格按照此规则递推时, l 2 l2 l2 l ′ l' l 冲突的方案数必为 0 0 0,因此没有判断 l 2 l2 l2 l ′ l' l 之间是否冲突的必要性。

  时间复杂度为 O ( m k 2 n + 3 ) O(mk2^{n+3}) O(mk2n+3),空间复杂度为 O ( m k 2 n + 2 ) O(mk2^{n+2}) O(mk2n+2)

  不 T L E \mathrm{TLE} TLE 就算成功。

import java.util.Scanner;

public class Main {

    public static void main(String[] args) { new Main().run(); }

    final int mod = 1000000007;

    void run() {
        Scanner in = new Scanner(System.in);
        int N = 1 << in.nextInt(), M = in.nextInt(), K = in.nextInt();
        int[][][][] dp = new int[M + 1][N][N][K + 1];
        dp[0][0][0][0] = 1;
        for (int m = 1; m <= M; m++)
            for (int now = 0; now < N; now++)
                for (int pr1 = 0; pr1 < N; pr1++)
                    if ((now << 2 & pr1) == 0 && (pr1 << 2 & now) == 0)
                        for (int pr2 = 0; pr2 < N; pr2++)
                            if ((now << 1 & pr2) == 0 && (pr2 << 1 & now) == 0)
                                for (int k = bitCount(now), g = k; k <= K; k++)
                                dp[m][now][pr1][k] = (dp[m][now][pr1][k] + dp[m - 1][pr1][pr2][k - g]) % mod;
        int ans = 0;
        for (int now = 0; now < N; now++)
            for (int pr1 = 0; pr1 < N; pr1++)
                if ((now << 2 & pr1) == 0 && (pr1 << 2 & now) == 0)
                    ans = (ans + dp[M][now][pr1][K]) % mod;
        System.out.println(ans);
    }

    public static 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;
    }
}
  • 0
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值