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


认真整下,不嘻嘻哈哈了。


#A 整数范围

本题总分:5 分


问题描述

  用 8 8 8 位二进制(一个字节)来表示一个非负整数,表示的最小值是 0 0 0,则一般能表示的最大值是多少?


答案提交

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


255

calcCode:

public class Test {
    
    

    public static void main(String[] args) {
    
    
        System.out.print(0xFF);
    }
}

?


#B 纯质数

本题总分:5 分


问题描述

  如果一个正整数只有 1 1 1 和它本身两个约数,则称为一个质数(又称素数)。
  前几个质数是: 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , ⋅ ⋅ ⋅ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, · · · 2,3,5,7,11,13,17,19,23,29,31,37,
  如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如: 2 , 3 , 5 , 7 , 23 , 37 2, 3, 5, 7, 23, 37 2,3,5,7,23,37 都是纯质数,而 11 , 13 , 17 , 19 , 29 , 31 11, 13, 17, 19, 29, 31 11,13,17,19,29,31 不是纯质数。当然 1 , 4 , 35 1, 4, 35 1,4,35 也不是纯质数。
  请问,在 1 1 1 20210605 20210605 20210605 中,有多少个纯质数?


答案提交

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


1903


预备知识


  如果不会一种合数筛法的,估计跑到比赛结束结果都出不来。

  质数打表

  就是为了这点题解,

  我才写的这篇博客。

  就是为了这篇博客,

  我才立的这个标题。


朴素解法


  一种朴素的想法,在打表 [ 1 , 20210605 ] [1,20210605] [1,20210605] 间的质数时,额外的进行一次纯质数效验,并将结果累加起来。

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

public class Test {
    
    

    public static final int N = 20210605;

    public static void main(String[] args) {
    
    
        boolean[] marked = new boolean[N + 1];
        List<Integer> primes = new ArrayList();
        marked[0] = marked[1] = true;
        int ans = 0;
        for (int i = 2; i <= N; i++) {
    
    
            if (!marked[i]) {
    
    
                primes.add(i);
                boolean flag = false;
                for (int k = i; k > 0; k /= 10)
                    if (flag = marked[k % 10]) break;
                if (!flag) ans++;
            }
            for (int p : primes) {
    
    
                if (p * i > N)  break;
                marked[p * i] = true;
                if (i % p == 0) break;
            }
        }
        System.out.println(ans);
    }
}


按位枚举


  在朴素的解法中,我们会发现,很多拆分判断一个质数是否是纯质数是没有必要的,因为在 [ 0 , 10 ) [0,10) [0,10) 中,质数仅占 { 2 , 3 , 5 , 7 } \{2,3,5,7\} { 2,3,5,7} 四位,如果仅判断这四个数字组合成的数是否是质数的话,性能能否有进一步的提升呢?

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

public class Test {
    
    

    public static final int N = 20210605;

    public static int[][] digits = new int[8][4];

    public static void main(String[] args) {
    
    
        boolean[] primes = new boolean[N + 1];
        List<Integer> helper = new ArrayList();
        Arrays.fill(primes, 2, N, true);
        int ans = 0;
        for (int i = 2; i <= N; i++) {
    
    
            if (primes[i]) helper.add(i);
            for (int p : helper) {
    
    
                if (p * i > N)  break;
                primes[p * i] = false;
                if (i % p == 0) break;
            }
        }
        digits[0] = new int[]{
    
    2, 3, 5, 7};
        for (int k = 1; k < 7; k++)
            for (int i = 0; i < 4; i++)
                digits[k][i] = digits[k - 1][i] * 10;
        digits[7] = new int[]{
    
     digits[6][0] * 10 };
        System.out.println(dfs(primes, 0, 0));
    }

    public static int dfs(boolean[] primes, int k, int depth) {
    
    
        if (depth == 8) return k <= N && primes[k] ? 1 : 0;
        int ans = primes[k] ? 1 : 0;
        for (int a : digits[depth])
            ans += dfs(primes, k + a, depth + 1);
        return ans;
    }
}

  实际上性能并没有进一步的提升,并且代码量还有所增加。

  这是因为在 ( 1 , N ] (1,N] (1,N] 这个范围中的质数大约有 N ln ⁡ N \cfrac{N}{\ln N} lnNN 个,而按位组成的可能是纯质数的数大约有 4 ln ⁡ N 4^{\ln N} 4lnN 个,这显然不是一个增值量级的。

  但这里放这么一段代码,是为了进一步扩展刷题较少的读者的解题思路,能掌握一种方法来解决其他的问题。


#C 完全日期

本题总分:10 分


问题描述

  如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日期。
  例如: 2021 2021 2021 6 6 6 5 5 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16 2 + 0 + 2 + 1 + 6 + 5 = 16 2+0+2+1+6+5=16,而 16 16 16 是一个完全平方数,它是 4 4 4 的平方。所以 2021 2021 2021 6 6 6 5 5 5 日是一个完全日期。
  例如: 2021 2021 2021 6 6 6 23 23 23 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16 2+0+2+1+6+2+3=16,是一个完全平方数。所以 2021 2021 2021 6 6 6 23 23 23 日也是一个完全日期。
  请问,从 2001 2001 2001 1 1 1 1 1 1 日到 2021 2021 2021 12 12 12 31 31 31 日中,一共有多少个完全日期?


答案提交

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


977


Java党的完全胜利


  LocalDate好用。


朴素解法


  枚举 [ 2001 [2001 [2001- 01 01 01- 01 01 01 2021 2021 2021- 12 12 12- 31 ] 31] 31] 这个区间间内的时间,判断然后累加。

import java.time.LocalDate;

public class Test {
    
    

    public static final int maxPerfect = 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9;

    public static final boolean[] perfect = new boolean[maxPerfect + 1];

    public static LocalDate start = LocalDate.of(2001, 01, 01);

    public static LocalDate end = LocalDate.of(2021, 12, 31);

    public static void main(String[] args) {
    
    
        int count = 0;
        for (int i = 1; i * i<= maxPerfect; i++)
            perfect[i * i] = true;
        while (end.compareTo(start) >= 0) {
    
    
            if (perfect[calc(start)])
                count++;
            start = start.plusDays(1);
        }
        System.out.println(count);
    }

    public static int calc(LocalDate date) {
    
    
        String dateStr = date.toString();
        int res = 0;
        for (int i = dateStr.length() - 1; i >= 0; i--)
            if (Character.isDigit(dateStr.charAt(i)))
                res += Character.digit(dateStr.charAt(i), 10);
        return res;
    }
}

朴素改进


  在 [ 2001 [2001 [2001- 01 01 01- 01 01 01 2021 2021 2021- 12 12 12- 31 ] 31] 31] 中,各数位之和不仅存在最大值 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9 = 32 2 + 0 + 1 + 9 + 0 + 9 + 2 + 9 = 32 2+0+1+9+0+9+2+9=32,还存在有最小值 2 + 0 + 0 + 1 + 0 + 1 + 0 + 1 2 + 0 + 0 + 1 + 0 + 1 + 0 + 1 2+0+0+1+0+1+0+1,也就是说,可能的完全平方数,不外乎 3 × 3 3 × 3 3×3 4 × 4 4 × 4 4×4 5 × 5 5 × 5 5×5 三个,就这一点我们来简化我们的代码。

import java.time.LocalDate;

public class Test {
    
    

    public static LocalDate start = LocalDate.of(2001, 01, 01);

    public static LocalDate end = LocalDate.of(2021, 12, 31);

    public static void main(String[] args) {
    
    
        int count = 0;
        while (end.compareTo(start) >= 0) {
    
    
            if (isPerfect(start)) count++;
            start = start.plusDays(1);
        }
        System.out.println(count);
    }

    public static boolean isPerfect(LocalDate date) {
    
    
        String dateStr = date.toString();
        int sum = 0;
        for (int i = dateStr.length() - 1; i >= 0; i--)
            if (Character.isDigit(dateStr.charAt(i)))
                sum += Character.digit(dateStr.charAt(i), 10);
        return sum == 3 * 3 || sum == 4 * 4 || sum == 5 * 5;
    }
}

不依赖 API 的实现


  先统计出平年月份和日期各数位之和的出现次数,然后遍历年份时额外的判断一道是否为闰年。

public class Test {
    
    

    public static void main(String[] args) {
    
    
        int[] bigMonth = {
    
     1, 3, 5, 7, 8, 1 + 0, 1 + 2 };
        int[] smallMonth = {
    
     4, 6, 9, 1 + 1 };
        int[] calendar = new int[9 + 2 + 9 + 1];
        int ans = 0;
        for (int day = 1; day <= 31; day++)
            for (int month : bigMonth)
                calendar[month + calc(day)]++;
        for (int day = 1; day <= 30; day++)
            for (int month : smallMonth)
                calendar[month + calc(day)]++;
        for (int day = 1; day <= 28; day++)
            calendar[2 + calc(day)]++;
        for (int year = 2001; year <= 2021; year++) {
    
    
            if (isLeapYear(year))
                if (isLeapYear(year + 2 + 2 + 9)) ans++;
            for (int i = 0; i < calendar.length; i++) {
    
    
                if (calendar[i] == 0) continue;
                if (isPerfect(calc(year) + i))
                    ans += calendar[i];
            }
        }
        System.out.println(ans);
    }

    public static int calc(int n) {
    
    
        int res = 0;
        do
            res += n % 10;
        while ((n /= 10) > 0);
        return res;
    }

    public static boolean isPerfect(int num) {
    
     return num == 3 * 3 || num == 4 * 4 || num == 5 * 5; }

    public static boolean isLeapYear(int year) {
    
     return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; }
}

#D 最小权值

本题总分:10 分


问题描述

  对于一棵有根二叉树 T T T,小蓝定义这棵树中结点的权值 W ( T ) W(T) W(T) 如下:
  空子树的权值为 0 0 0
  如果一个结点 v v v 有左子树 L L L, 右子树 R R R,分别有 C ( L ) C(L) C(L) C ( R ) C(R) C(R) 个结点,则 W ( v ) = 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) W(v) = 1 + 2W(L) + 3W(R) + (C(L))^{2} C(R) W(v)=1+2W(L)+3W(R)+(C(L))2C(R)
  树的权值定义为树的根结点的权值。
  小蓝想知道,对于一棵有 2021 2021 2021 个结点的二叉树,树的权值最小可能是多少?


答案提交

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


2653631372


记忆化搜索


那可真蠢

这题就是把动规两个字放在了题面上,没什么好说的。

public class Test {
    
    

    public static final int N = 2021;

    public static long[] map = new long[N + 1];

    public static void main(String[] args) {
    
     System.out.println(dfs(N)); }

    public static long dfs(int TSize) {
    
    
        if (TSize == 0 || map[TSize] != 0) return map[TSize];
        long TWeight = Long.MAX_VALUE;
        for (int LC = 0; LC < TSize; LC++) {
    
    
            int RC = TSize - LC - 1;
            long weight =
                1 + 2 * dfs(LC) + 3 * dfs(RC) +  LC * LC * RC;
            TWeight = Math.min(TWeight, weight);
        }
        return map[TSize] = TWeight;
    }
}

动态规划


  根据题意,显然有 N = 2021 N = 2021 N=2021 W ( 0 ) = 0 W(0) = 0 W(0)=0 W ( N ) = min ⁡ { 1 + 2 W ( L ) + 3 W ( R ) + ( C ( L ) ) 2 C ( R ) } W(N) = \min \{1 + 2W(L) + 3W(R) + (C(L))^{2}C(R)\} W(N)=min{ 1+2W(L)+3W(R)+(C(L))2C(R)},其中 L + R = N − 1 L + R = N - 1 L+R=N1 0 ≤ L ≤ R ≤ N 0 \leq L \leq R \leq N 0LRN

  状态转移方程就在脸上: d p ( y ) = { 0 i = 0 min ⁡ { 1 + 2 d p ( l ) + 3 d p ( r ) + l 2 r } l + r = i − 1 , 0 ≤ l ≤ r ≤ i dp(y)=\left\{ \begin{array}{lr} 0&i = 0\\ \min \{1 + 2dp(l) + 3dp(r) + l^{2}r\}&l + r = i - 1,0 \leq l \leq r \leq i \end{array} \right. dp(y)={ 0min{ 1+2dp(l)+3dp(r)+l2r}i=0l+r=i10lri  懂得都懂。

public class Test {
    
    

    public static final int N = 2021;

    public static void main(String[] args) {
    
    
        long[] dp = new long[N + 1];
        for (int i = 1; i <= N; i++) {
    
    
            dp[i] = Long.MAX_VALUE;
            for (int l = i >> 1; l >= 0; l--)
                dp[i] = Math.min(dp[i],
                    1 + 2 * dp[l] + 3 * dp[i - l - 1] + l * l * (i - l - 1));
        }
        System.out.println(dp[N]);
    }
}

#E 大写

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


问题描述

  给定一个只包含大写字母和小写字母的字符串,请将其中所有的小写字母转换成大写字母后将字符串输出。


输入格式

  输入一行包含一个字符串。


输出格式

  输出转换成大写后的字符串。


测试样例1

Input:
LanQiao

Output:
LANQIAO

评测用例规模与约定

  对于所有评测用例,字符串的长度不超过 100 100 100


code:

public class Main {
    
    

    public static void main(String[] args) {
    
    
        System.out.println(new java.util.Scanner(System.in).next().toUpperCase());
    }
}

送分。


#F 123

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


问题描述

  小蓝发现了一个有趣的数列,这个数列的前几项如下:
   1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , . . . 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, ... 1,1,2,1,2,3,1,2,3,4,...
  小蓝发现,这个数列前 1 1 1 项是整数 1 1 1,接下来 2 2 2 项是整数 1 1 1 2 2 2,接下来 3 3 3 项是整数 1 1 1 3 3 3,接下来 4 4 4 项是整数 1 1 1 4 4 4,依次类推。
  小蓝想知道,这个数列中,连续一段的和是多少。


输入格式

  输入的第一行包含一个整数 T T T,表示询问的个数。
  接下来 T T T 行,每行包含一组询问,其中第 i i i 行包含两个整数 l i l_{i} li r i r_{i} ri,表示询问数列中第 l i l_{i} li 个数到第 r i r_{i} ri 个数的和。


输出格式

  输出 T T T 行,每行包含一个整数表示对应询问的答案。


测试样例1

Input:
3
1 1
1 3
5 8

Output:
1
4
8

评测用例规模与约定

  对于 10 10 10% 的评测用例, 1 ≤ T ≤ 30 , 1 ≤ l i ≤ r i ≤ 100 1 \leq T \leq 30, 1 \leq l_{i} \leq r_{i} ≤ 100 1T30,1liri100
  对于 20 20 20% 的评测用例, 1 ≤ T ≤ 100 , 1 ≤ l i ≤ r i ≤ 1000 1 \leq T \leq 100, 1 \leq l_{i} \leq r_{i} ≤ 1000 1T100,1liri1000
  对于 40 40 40% 的评测用例, 1 ≤ T ≤ 1000 , 1 ≤ l i ≤ r i ≤ 1 0 6 1 \leq T \leq 1000, 1 \leq l_{i} \leq r_{i} ≤ 10^{6} 1T1000,1liri106
  对于 70 70 70% 的评测用例, 1 ≤ T ≤ 10000 , 1 ≤ l i ≤ r i ≤ 1 0 9 1 \leq T \leq 10000, 1 \leq l_{i} \leq r_{i} ≤ 10^{9} 1T10000,1liri109
  对于 80 80 80% 的评测用例, 1 ≤ T ≤ 1000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 1000, 1 \leq l_{i} \leq r_{i} ≤ 10^{12} 1T1000,1liri1012
  对于 90 90 90% 的评测用例, 1 ≤ T ≤ 10000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 10000, 1 \leq l_{i} \leq r_{i} ≤ 10^{12} 1T10000,1liri1012
  对于所有评测用例, 1 ≤ T ≤ 100000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 100000, 1 \leq l_{i} \leq r_{i} ≤ 10^{12} 1T100000,1liri1012


前缀和


  区间和问题,一般先考虑的都是使用前缀和,但这里给出的区间范围太大,以至于用全部的内存来存放前缀和,能通过的用例可能也只有半数,因此这里要将原序列 [ 1 , 1 , 2 , 1 , 2 , 3 , 1 ,   ⋯   , n − 1 , n ] \pmb[1,1,2,1,2,3,1,\ \cdots,n-1,n\pmb] [[[1,1,2,1,2,3,1, ,n1,n]]]变形为矩阵 [ 1 1 2 1 2 3 ⋮ ⋮ ⋮ ⋱ 1 2 3 ⋯ n ] \begin{bmatrix}1\\1&2\\1&2&3\\\vdots&\vdots&\vdots&\ddots\\1&2&3&\cdots&n\end{bmatrix} 111122233n为了能涵盖 [ 1 , 1 0 12 ] [1,10^{12}] [1,1012] 间的查询,即矩阵包含 1 0 12 10^{12} 1012 个元素,这里 n n n n = 2 E 12 n = \sqrt{2E12} n=2E12

  对于序列变形的矩阵,我们分别求出列与最后一行的前缀和后,组合起来就能在对数时间内 (需要二分查找给定 k 所在的行) 得到任意 [ 1 , k ] [1,k] [1,k] k ∈ [ 1 , 1 0 12 ] k \in [1,10^{12}] k[11012] 间内的元素和,对于任意 ∑ i = l r a i \sum_{i = l}^{r} a_{i} i=lrai,我们只需转换为 ∑ i = 1 r a i − ∑ i = 1 l − 1 a i \sum_{i = 1}^{r} a_{i} - \sum_{i = 1}^{l - 1} a_{i} i=1raii=1l1ai 问题就被解决了。

  还有就是溢出问题,形如这种矩阵,在最大元素为 n n n 时,其矩阵元素和为 n + 2 ( n − 1 ) + 3 ( n − 2 ) + ⋯ + n n + 2(n - 1) + 3(n - 2) + \cdots + n n+2(n1)+3(n2)++n
   = n ( 1 + 2 + 3 + ⋯ + n ) − n ( 1 × 2 + 2 × 3 + 3 × 4 + ⋯ + ( n − 1 ) n ) =n(1 +2+3+\cdots+n)-n(1×2+2×3+3×4+\cdots+(n - 1)n) =n(1+2+3++n)n(1×2+2×3+3×4++(n1)n)
   = n 2 ( n − 1 ) 2 − ( 1 2 + 1 + 2 2 + 2 + 3 2 + ⋯ + ( n − 1 ) 2 + n − 1 ) =\cfrac{n^{2}(n-1)}{2} - (1^2+1 + 2^2 + 2 + 3^2 +\cdots +(n - 1)^2 + n - 1) =2n2(n1)(12+1+22+2+32++(n1)2+n1)
   = n 2 ( n − 1 ) 2 − { ( 1 2 + 2 2 + 3 2 + ⋯ + ( n − 1 ) 2 ) + ( 1 + 2 + 3 + ⋯ + n − 1 ) } =\cfrac{n^{2}(n-1)}{2} - \{(1^2 + 2^2 + 3^2 +\cdots +(n - 1)^2) + (1 + 2 + 3 + \cdots + n - 1)\} =2n2(n1){ (12+22+32++(n1)2)+(1+2+3++n1)}
   = n 2 ( n − 1 ) 2 − n ( n − 1 ) ( 2 n − 1 ) 6 − n ( n − 1 ) 2 =\cfrac{n^{2}(n-1)}{2} - \cfrac{n(n - 1)(2n - 1)}{6}-\cfrac{n(n-1)}{2} =2n2(n1)6n(n1)(2n1)2n(n1)

  取 n n n 2 E 12 \sqrt{2E12} 2E12 ,矩阵元素和约为 4.7 E 17 4.7E17 4.7E17,长整形够用。


code:

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

public class Main {
    
    

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

    int N = (int)Math.sqrt(2E12) + 1;
    long[] row = new long[N + 1], col = new long[N + 1];

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        PrintWriter out = new PrintWriter(System.out);
        for (int i = 1; i <= N; i++) {
    
    
            row[i] = i + row[i - 1];
            col[i] = col[i - 1] + row[i];
        }
        int T = in.readInt();
        long l, r;
        while (T-- > 0) {
    
    
            l = in.readLong();
            r = in.readLong();
            out.println(sum(r) - sum(l - 1));
        }
        out.flush();
    }

    long sum(long r) {
    
    
        int k = lowerBound(r);
        return r == row[k] ? col[k] : col[k - 1] + row[(int)(r - row[k - 1])];
    }

    int lowerBound(long k) {
    
    
        int offset = 0, length = N + 1;
        while (length > 0) {
    
    
            int half = length >> 1;
            if (k > row[offset + half]) {
    
    
                offset += half + 1;
                length -= half + 1;
            } else length = half;
        }
        return offset;
    }

    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()); }

        long readLong() {
    
     return Long.parseLong(read()); }
    }
}


#G 和与乘积

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


问题描述

  给定一个数列 A = ( a 1 , a 2 , ⋯   , a n ) A = (a_{1}, a_{2}, \cdots, a_{n}) A=(a1,a2,,an),问有多少个区间 [ L , R ] [L, R] [L,R] 满足区间内元素的乘积等于他们的和,即 a L ⋅ a L + 1 ⋯ ⋅ a R = a L + a L + 1 + ⋯ + a R a_{L} · a_{L+1} \cdots · a_{R} = a_{L} + a_{L+1} + \cdots + a_{R} aLaL+1aR=aL+aL+1++aR


输入格式

  输入第一行包含一个整数 n n n,表示数列的长度。
  第二行包含 n n n 个整数,依次表示数列中的数 a 1 , a 2 , ⋯   , a n a_{1}, a_{2}, \cdots, a_{n} a1,a2,,an


输出格式

  输出仅一行,包含一个整数表示满足如上条件的区间的个数。


测试样例1

Input:
4
1 3 2 2

Output:
6

Explanation:
符合条件的区间为 [1, 1], [1, 3], [2, 2], [3, 3], [3, 4], [4, 4]。

评测用例规模与约定

  对于 20 20 20% 的评测用例, n ≤ 3000 n \leq 3000 n3000
  对于 50 50 50% 的评测用例, n ≤ 20000 n \leq 20000 n20000
  对于所有评测用例, 1 ≤ n ≤ 200000 , 1 ≤ a i ≤ 200000 1 \leq n \leq 200000, 1 \leq ai \leq 200000 1n200000,1ai200000


优雅骗分


  推了一天,啥也没推出来,放弃了。

  面对这种朴素思路复杂度在 O ( n 2 ) O(n^{2}) O(n2) 的问题,我们可以选择一个不可能的元素将其折半,假设这个元素在序列的第 m m m 个,折半后的复杂度就在 O ( m 2 + ( n − m ) 2 ) O(m^{2} + (n - m)^{2}) O(m2+(nm)2)

  理论上, m m m 越小且每个点分布的越均匀,那么安装这种方法拆分,复杂度可以将至 O ( n log ⁡ n ) O(n \log n) O(nlogn) 甚至 O ( n ) O(n) O(n)

  在 a L ⋅ a L + 1 ⋯ ⋅ a R = a L + a L + 1 + ⋯ + a R a_{L} · a_{L+1} \cdots · a_{R} = a_{L} + a_{L+1} + \cdots + a_{R} aLaL+1aR=aL+aL+1++aR 这个表达式中,若 R − L > 1 R - L >1 RL>1 的情况,两边必为合数。

  如果任意 a L ⋅ a L + 1 ⋯ ⋅ a R a_{L} · a_{L+1} \cdots · a_{R} aLaL+1aR 中存在一个较大的质数 p p p,那么有极大的可能 a L + a L + 1 + ⋯ + a R a_{L} + a_{L+1} + \cdots + a_{R} aL+aL+1++aR 不能将其整除,也就是任给若干个 ( 0 , 2 × 1 0 5 ] (0,2×10^{5}] (0,2×105] 间的自然数,他们的和 p p p 的和是 p p p 的倍数概率,

  不会算,反正很低就对了,我们可以根据此将原序列分成期望若干个互不干涉的子序列。

  运气好就A了。


import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class Main {
    
    

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

    final int N = 200000, p = 131;

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        int n = in.readInt(), ans = n;
        int[] A = new int[n];
        for (int i = 0; i < n; i++)
            A[i] = in.readInt();
        if (n > 0xC60) {
    
    
            boolean[] compos = new boolean[N + 1];
            List<Integer> prime = new ArrayList();
            compos[1] = true;
            for (int i = 2; i <= N; i++) {
    
    
                if (!compos[i]) prime.add(i);
                for (int p : prime) {
    
    
                    if (p * i > n) break;
                    compos[p * i] = true;
                    if (i % p == 0)break;
                }
            }
            for (int i = 0, j = 0; i < n; i = j++) {
    
    
                while (j < n && (compos[A[i]] || A[i] >= p)) j++;
                ans += calc(A, i, j - i);
            }
        } else ans += calc(A, 0, n);
        System.out.println(ans);
    }

    int calc(int[] A, int offset, int length) {
    
    
        int ans = 0;
        long sum, product;
        for (int i = 0; i < length; i++) {
    
    
            sum = product = A[offset + i];
            for (int j = i + 1; j < length; j++) {
    
    
                product = multi(product, A[offset + j]);
                if (product < 0) break;
                sum += A[offset + j];
                if (sum == product)
                    ans++;
            }
        }
        return ans;
    }

    long multi(long arg0, long arg1) {
    
    
        long product = arg0 * arg1;
        return product / arg1 == arg0 ? product : -1;
    }

    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()); }
    }
}

  给这题锤的神志不清了,有无大哥指点一下迷津。


前缀和


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

public class Main {
    
    

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

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        int n = in.readInt(), ans = n, N = 1, a;
        long[] A = new long[n + 1];
        long[] S = new long[n + 1];
        int[] O = new int[n + 2];
        for (int i = 0; i < n; i++) {
    
    
            a = in.readInt();
            if (a == 1) {
    
    
                S[N]++;
                O[N]++;
            } else {
    
    
                S[N] += S[N - 1] + a;
                A[N++] = a;
            }
        }
        long max = S[N - 1] + O[N];
        for (int i = 1; i < N; i++) {
    
    
            long pro = A[i];
            for (int j = i + 1; j < N; j++) {
    
    
                pro *= A[j];
                if (pro > max) break;
                long dif = pro - S[j] + S[i - 1] + O[i];
                if (dif == 0) ans++;
                else if (dif > 0 && O[i] + O[j + 1] >= dif) {
    
    
                    long l = Math.min(dif, O[i]);
                    long r = Math.min(dif, O[j + 1]);
                    ans += l + r - dif + 1;
                }
            }
        }
        System.out.println(ans);
    }

    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()); }
    }
}

#H 巧克力

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


问题描述

  小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。
  一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的巧克力,请问小蓝最少花多少钱能买到让自己吃 x x x 天的巧克力。


输入格式

  输入的第一行包含两个整数 x , n x, n x,n,分别表示需要吃巧克力的天数和巧克力的种类数。
  接下来 n n n 行描述货架上的巧克力,其中第 i i i 行包含三个整数 a i , b i , c i a_{i}, b_{i}, c_{i} ai,bi,ci,表示第 i i i 种巧克力的单价为 a i a_{i} ai,保质期还剩 b i b_{i} bi 天(从现在开始的 b i b_{i} bi 天可以吃),数量为 c i c_{i} ci


输出格式

  输出一个整数表示小蓝的最小花费。如果不存在让小蓝吃 x x x 天的购买方案,输出 − 1 −1 1


测试样例1

Input:
10 3
1 6 5
2 7 3
3 10 10

Output:
18

Explanation:
一种最佳的方案是第 1 种买 5 块,第 2 种买 2 块,第 3 种买 3 块。前 5 天吃第 1 种,第 6、7 天吃第 2 种,第 8 至 10 天吃第 3 种。

评测用例规模与约定

  对于 30 30 30% 的评测用例, n , x ≤ 1000 n, x \leq 1000 n,x1000
  对于所有评测用例, 1 ≤ n , x ≤ 100000 1 \leq n, x \leq 100000 1n,x100000 1 ≤ a i , b i , c i ≤ 1 0 9 1 \leq a_{i}, b_{i}, c_{i} \leq 10^{9} 1ai,bi,ci109


贪心算法


  优先考虑最便宜的巧克力,每种巧克力尽可能晚的吃,直至已经吃了 x x x 天或再无可选择的巧克力。

  简单证明一下就是,如果存在比当前方案所有巧克力都便宜的巧克力,插入到任意可行的位置都会使当前方案的花费减小,因此按价格升序考虑巧克力的安排。

  然后就是当前在一个日期选择的巧克力要尽可能的不影响到后面巧克力的选择,而在一个当前最便宜的巧克力的保质期的区间,只会出现当前巧克力可选,后继巧克力也可选和当前巧克力可选,后继巧克力不可选两种情况,显然在都可选情况里选当前巧克力最优,而在后继不可选情况里,越靠近保质期结束对后继巧克力的选择影响最小。

  狗屁不通真就那个论证失败但合理能让代码跑过样例。


import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    
    

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

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        int x = in.readInt(), n = in.readInt();
        boolean[] calender = new boolean[x + 1];
        int[][] chocos = new int[n][3];
        long ans = 0, marked = 0;
        for (int i = 0; i < n; i++) {
    
    
            chocos[i][0] = in.readInt();
            chocos[i][1] =
                min(x, in.readInt());
            chocos[i][2] = in.readInt();
        }
        Arrays.sort(chocos,
            (a1, a2)->(a1[0] - a2[0]));
        for (int[] choco : chocos)
            while (choco[1] > 0 && choco[2] > 0) {
    
    
                if (!calender[choco[1]]) {
    
    
                    calender[choco[1]] = true;
                    ans += choco[0];
                    choco[2]--;
                    marked++;
                }
                choco[1]--;
            }
        System.out.println(marked == x ? ans : -1);
    }

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

    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()); }
    }
}

  可是朴素贪心的去求解这个问题,以这个解法为例,时间复杂度在 O ( n log ⁡ n + n max ⁡ ( b i , x ) ) O(n \log n + n \max (b_{i}, x)) O(nlogn+nmax(bi,x)),接近 O ( n 2 ) O(n^2) O(n2) 显然在这个数据规律下,还是相当不理想的。

  不过这种动态更新标记,查找最小可用,这和一个常用数据结构很契合。


并查集优化


  在确定完贪心策略后,程序中很大的一个性能瓶颈,出现在查找保质期内最大未规划巧克力的时间。

  如果我们将一串规划好的时间按原顺序组成链表,那么在链头大于一的情况下,链头左边的元素,就是我们要找的值。

  并且不需要关心这样组织起的链表其他信息,我们只需上述的信息就能优化整个查找操作,并查集就能完成这项任务,

  并且在路径压缩的基础上,这个操作的时间复杂程度只有 O ( 1 ) O(1) O(1)


import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    
    

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

    int[] link;

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        int x = in.readInt(), n = in.readInt();
        int[][] chocos = new int[n][3];
        long ans = 0, marked = 0;
        link = new int[x + 1];
        for (int i = 0; i < n; i++) {
    
    
            chocos[i][0] = in.readInt();
            chocos[i][1] =
                min(x, in.readInt());
            chocos[i][2] = in.readInt();
        }
        Arrays.fill(link, -1);
        Arrays.sort(chocos,
            (a1, a2)->(a1[0] - a2[0]));
        for (int[] choco : chocos)
            while (choco[2]-- > 0) {
    
    
                int d = find(choco[1]);
                if (d > 0) {
    
    
                    link[d] = d - 1;
                    ans += choco[0];
                    marked++;
                } else break;
            }
        System.out.println(marked == x ? ans : -1);
    }

    int find(int x) {
    
     return link[x] == -1 ? x : (link[x] = find(link[x])); }

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

    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()); }
    }
}

  时间复杂度为 O ( n log ⁡ n + n + x ) O(n \log n + n + x) O(nlogn+n+x),即 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  当然这里影响运行速度的参数不止 n n n 一个,

  能力有限,这里不做过多的讨论。


贪心 + 大根堆


  为了能部分屏蔽除 n n n 以外的变量对程序整体运行时间的影响,我们需要按种而不是按块来讨论巧克力的规划。

  但在上述贪心策略过程中,待规划的日期是不连续的,如果将可安排日期按段查找,付出的代价可能远比之前要大,额外增加的代码量相应的也会增加调试的风险与成本。

  容易地再想到一个策略,对于每个日期 d d d,在不选择变质巧克力的前提下,尽可能的选择最便宜的 d d d 个巧克力。

  对此我们可以按保质期升序考虑每种巧克力,动态的维护选择方案。

 更详细的说:

  建立一个以价格为权值的大根堆,该堆代表已选中的巧克力,堆初始为空。

 按保质期升序枚举巧克力的种类,枚举时会遇见两种情况:

 1、当前巧克力数量 + 当前已选巧克力数量 ≤ \leq 当前巧克力保质期

  这时我们视为选择当前巧克力,直接将其插入到堆里。

 2、当前巧克力数量 + 当前已选巧克力数量 > > > 当前巧克力保质期

  我们先计算出两项的不等式两边的绝对差,然后从堆中弹出价格大于当前巧克力的巧克力,并且数量不能大于这个差值,然后插入若干当前种类的巧克力,使得当前已选巧克力数量等于当前巧克力保质期。

  可以预见的是,最终如果第 d d d 便宜的巧克力没有加入第 d d d 天的方案,这就意味着在第 d d d 便宜的巧克力保质期内,有更便宜的巧克力使得第 d d d 便宜的巧克力无法加入,所以这个贪心策略下的结果是最优的。

  最终,堆内的巧克力就是最优的选择方案。

  当然,它可能排不满 x x x 天。


\\ 不想写

#I 翻转括号序列

时间限制: 10.0 10.0 10.0s 内存限制: 768.0 768.0 768.0MB 本题总分: 25 25 25


问题描述

  给定一个长度为 n n n 的括号序列,要求支持两种操作:
   1 1 1. 将 [ L i , R i ] [L_{i}, R_{i}] [Li,Ri] 区间内(序列中的第 L i L_{i} Li 个字符到第 R i R_{i} Ri 个字符)的括号全部翻转(左括号变成右括号,右括号变成左括号)。
   2 2 2. 求出以 Li 为左端点时,最长的合法括号序列对应的 R i R_{i} Ri (即找出最大的 R i R_{i} Ri 使 [ L i , R i ] [L_{i}, R_{i}] [Li,Ri] 是一个合法括号序列)。


输入格式

  输入的第一行包含两个整数 n , m n, m n,m,分别表示括号序列长度和操作次数。
  第二行包含给定的括号序列,括号序列中只包含左括号和右括号。
  接下来 m m m 行,每行描述一个操作。如果该行为 “ 1 L i R i ” “1 L_{i} R_{i}” 1LiRi,表示第一种操作,区间为 [ L i , R i ] [L_{i}, R_{i}] [Li,Ri];如果该行为 “ 2 L i ” “2 L_{i}” 2Li 表示第二种操作,左端点为 L i L_{i} Li


输出格式

  对于每个第二种操作,输出一行,表示对应的 R i R_{i} Ri。如果不存在这样的 R i R_{i} Ri,请输出 0 0 0


测试样例1

Input:
7 5
((())()
2 3
2 2
1 3 5
2 3
2 1

Output:
4
7
0
0

评测用例规模与约定

  对于 20 20 20% 的评测用例, n , m ≤ 5000 n, m \leq 5000 n,m5000
  对于 40 40 40% 的评测用例, n , m ≤ 30000 n, m \leq 30000 n,m30000
  对于 60 60 60% 的评测用例, n , m ≤ 100000 n, m \leq 100000 n,m100000
  对于所有评测用例, 1 ≤ n ≤ 1 0 6 , 1 ≤ m ≤ 2 × 1 0 5 1 \leq n \leq 10^{6}, 1 \leq m \leq 2 × 10^{5} 1n106,1m2×105


线段树


  对于一个合法的括号序列,

  它总是以 ‘(’ 开始,使用栈来判断一个序列是否为合法的单调序列,即栈内只存放 ‘(’ ,遍历序列时遇到 ‘(’ 压入栈里,遇到 ‘)’ 若栈为空则序列非法,否则弹出栈顶 ‘(’,若遍历完成栈为空,则序列合法,否则序列非法。

  很容易发现 ‘(’ 对栈大小的贡献为 ‘ 1 1 1’,而 ‘)’ 对栈大小的贡献为 ‘ − 1 -1 1’,一个合法的括号序列始终不会为栈空时遇到 ‘)’ 。

  我们可以用括号序列构建 ± 1 \pm1 ±1 序列,并计算出它的前缀和数组 s u m sum sum

  按照上述性质,一个括号序列合法的充分条件是 s u m [ L − 1 ] = s u m [ R ]   且   ∀ m ∈ [ L , R ] 都 有 s u m [ m ] ≥ s u m [ L − 1 ] sum[L - 1] = sum[R]\ 且\ \forall m \in[L,R]都有sum[m] \ge sum[L - 1] sum[L1]=sum[R]  m[L,R]sum[m]sum[L1]  再证其必要性,

  若有上述条件,则区间 s u m [ L , R ] sum[L,R] sum[L,R] 存在峰值 s u m [ k ] sum[k] sum[k],而原括号序列 k k k 位必然为 ‘(’ ,因为 k k k 为峰值,所以 s u m [ k + 1 ] = s u m [ k ] − 1 sum[k + 1] = sum[k] - 1 sum[k+1]=sum[k]1,即原括号序列 k + 1 k + 1 k+1 位必然为 ‘)’ , [ k , k + 1 ] [k,k+1] [k,k+1] 为一个合法的括号序列,将其从序列中剔除,如存在多个峰值则同时剔除多个,重复此过程直至序列为空。

  证毕。

  现在将问题转变为查找区间最小值,如果我们动态维护一个线段树的话,同时我们也能使用懒节点优化区间的翻转。


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

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();
        String str = in.read();
        int[] sum = new int[n + 1];
        for (int i = 1; i <= n; i++)
            sum[i] = sum[i - 1] + (str.charAt(i - 1) == '(' ? 1 : -1);
        Tree tree = new Tree(sum);
        int c = 1;
        while (m-- > 0) {
    
    
            int q = in.readInt(), L, R, ans;
            if (q == 1) {
    
    
                L = in.readInt();
                R = in.readInt();
                tree.turn(L, R);
            } else {
    
    
                L = in.readInt();
                int key = (L == 1 ? 0 : tree.query(L - 1));
                ans = tree.firstLower(L, n, key);
                ans = ans == 0 ? n : ans - 1;
                ans = tree.lastLower(L, ans, key + 1);
                out.println(ans == L ? 0 : ans);
            }
        }
        out.flush();
    }

    class Tree {
    
    

        int size;

        Node root;

        Tree(int[] values) {
    
     root = build(1, size = values.length - 1, values); }

        void update(int L, int R, int[][] matrix, Node node) {
    
    
            if (node.L >= L && node.R <= R) node.tag(matrix);
            else {
    
    
                pushDown(node);
                if (node.left.R >= L) update(L, R, matrix, node.left);
                if (node.right.L <= R) update(L, R, matrix, node.right);
                pushUp(node);
            }
        }

        int query(int index) {
    
     return query(index, root); }

        int query(int index, Node node) {
    
    
            if (node.L == node.R) return node.max;
            else {
    
    
                pushDown(node);
                int res = query(index, node.left.R >= index ? node.left : node.right);
                pushUp(node);
                return res;
            }
        }

        int[][] T = {
    
    {
    
    -1, 0}, {
    
    0, 1}};

        void turn(int L, int R) {
    
    
            if (L > 1) {
    
    
                update(1, L - 1, T, root);
                int k = query(L - 1);
                update(L, size, new int[][]{
    
    {
    
    1, 0}, {
    
    2 * k, 1}}, root);
            }
            update(1, R, T, root);
            if (R < size) {
    
    
                int k = query(R);
                update(R + 1, size, new int[][]{
    
    {
    
    1, 0}, {
    
    2 * k, 1}}, root);
            }
        }

        int firstLower(int L, int R, int key) {
    
     return firstLower(L, R, key, root); }

        int firstLower(int L, int R, int key, Node node) {
    
    
            if (node.L == node.R) return node.L;
            else {
    
    
                int res = 0;
                pushDown(node);
                if (node.left.R >= L && node.left.min() < key)
                    res = firstLower(L, R, key, node.left);
                if (res == 0 && node.right.L <= R && node.right.min() < key)
                    res = firstLower(L, R, key, node.right);
                pushUp(node);
                return res;
            }
        }

        int lastLower(int L, int R, int key) {
    
     return lastLower(L, R, key, root); }

        int lastLower(int L, int R, int key, Node node) {
    
    
            if (node.L == node.R) return node.L;
            else {
    
    
                int res = 0;
                pushDown(node);
                if (node.right.L <= R && node.right.min() < key)
                    res = lastLower(L, R, key, node.right);
                if (res == 0 && node.left.R >= L && node.left.min() < key)
                    res = lastLower(L, R, key, node.left);
                pushUp(node);
                return res;
            }
        }

        void pushUp(Node node) {
    
    
            node.max = max(node.left.max(), node.right.max());
            node.min = min(node.left.min(), node.right.min());
        }

        void pushDown(Node node) {
    
    
            if (node.lazy != null) {
    
    
                node.left.tag(node.lazy);
                node.right.tag(node.lazy);
                node.lazy = null;
            }
        }

        Node build(int L, int R, int[] values) {
    
    
            if (L == R) return new Node(L, values[L]);
            else {
    
    
                Node node = new Node();
                int mid = L + R >> 1;
                node.left = build(L, mid, values);
                node.right = build(mid + 1, R, values);
                pushUp(node);
                node.L = L;
                node.R = R;
                return node;
            }
        }

        int max(int arg1, int arg2) {
    
     return arg1 > arg2 ? arg1 : arg2; }

        int min(int arg1, int arg2) {
    
     return arg1 < arg2 ? arg1 : arg2; }

        class Node {
    
    

            Node left, right;

            int max, min;

            int[][] lazy;

            int L, R;

            Node() {
    
     super(); }

            Node(int index, int value) {
    
    
                max = min = value;
                L = R = index;
            }

            int max() {
    
     return max > min ? max : min; }

            int min() {
    
     return max < min ? max : min; }

            void tag(int[][] matrix) {
    
    
                if (lazy == null) lazy = matrix;
                else {
    
    
                    int[][] A = new int[2][2];
                    A[0][0] = lazy[0][0] * matrix[0][0] + lazy[0][1] * matrix[1][0];
                    A[0][1] = lazy[0][0] * matrix[0][1] + lazy[0][1] * matrix[1][1];
                    A[1][0] = lazy[1][0] * matrix[0][0] + lazy[1][1] * matrix[1][0];
                    A[1][1] = lazy[1][0] * matrix[0][1] + lazy[1][1] * matrix[1][1];
                    lazy = A;
                }
                max = max * matrix[0][0] + matrix[1][0];
                min = min * matrix[0][0] + matrix[1][0];
            }
        }
    }

    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()); }

        long readLong() {
    
     return Long.parseLong(read()); }
    }
}

这题出的纯恶心选手。


#J 异或三角

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


问题描述

  给定 T T T 个数 n 1 , n 2 , ⋅ ⋅ ⋅ , n T n_{1}, n_{2}, · · · , n_{T} n1,n2,,nT,对每个 n i n_{i} ni 请求出有多少组 a , b , c a, b, c a,b,c 满足:
   1 1 1. 1 ≤ a , b , c ≤ n i 1 \leq a, b, c \leq n_{i} 1a,b,cni
   2 2 2. a ⊕ b ⊕ c = 0 a \oplus b \oplus c = 0 abc=0,其中 ⊕ \oplus 表示二进制按位异或;
   3 3 3. 长度为 a , b , c a, b, c a,b,c 的三条边能组成一个三角形。


输入格式

  输入的第一行包含一个整数 T T T
  接下来 T T T 行每行一个整数,分别表示 n 1 , n 2 , ⋅ ⋅ ⋅ , n T n_{1}, n_{2}, · · · , n_{T} n1,n2,,nT


输出格式

  输出 T T T 行,每行包含一个整数,表示对应的答案。


测试样例1

Input:
2
6
114514

Output:
6
11223848130

评测用例规模与约定

  对于 10 10 10% 的评测用例, T = 1 , 1 ≤ n i ≤ 200 T = 1, 1 \leq n_{i} \leq 200 T=1,1ni200
  对于 20 20 20% 的评测用例, T = 1 , 1 ≤ n i ≤ 2000 T = 1, 1 \leq n_{i} \leq 2000 T=1,1ni2000
  对于 50 50 50% 的评测用例, T = 1 , 1 ≤ n i ≤ 2 20 T = 1, 1 \leq n_{i} \leq 2^{20} T=1,1ni220
  对于 60 60 60% 的评测用例, 1 ≤ T ≤ 100000 , 1 ≤ n i ≤ 2 20 1 \leq T \leq 100000, 1 \leq n_{i} \leq 2^{20} 1T100000,1ni220
  对于所有评测用例, 1 ≤ T ≤ 100000 , 1 ≤ n i ≤ 2 30 1 \leq T ≤ 100000, 1 \leq n_{i} \leq 2^{30} 1T100000,1ni230


  ?这数据


线性递推


  都线性了,那 40 40 40% 的 2 30 2^{30} 230 就不用想了。

  先是要确定几个能帮助我们加快程序运行速度的性质。

   a ⊕ a = 0 a \oplus a = 0 aa=0 0 ⊕ 0 = 0 0 \oplus 0 = 0 00=0,因此 a , b , c a,b,c a,b,c 互不相等,对于最终计算出的方案数,我们只需在计算时满足 a > b > c a > b > c a>b>c,然后对结果乘以 3 ! 3! 3!

  而 a < b + c a < b + c a<b+c 才能组成三角形,故需要满足 a = b ⊕ c < b + c a = b \oplus c < b + c a=bc<b+c

  我们都知道异或还有个别名,模二意义下的加法,也就是两个数做异或运算相当在二进制下做加法并舍弃进位。

  因此当 b b b c c c 有任意一位同为 1 1 1 时,加法运算发生进位,不等式成立。

  再考虑对任意 a a a 可能的方案数,

   a ≥ 1 a \ge 1 a1,因此 a a a 能被表示为 1 x 1 x 2 ⋯ x m 1x_{1}x_{2}\cdots x_{m} 1x1x2xm 这种 1 1 1 接后继二进制串的形式;为了使 a > b > c a > b > c a>b>c b b b c c c 的二进制表示长度不会大于 a a a;为了使 a = b ⊕ c a = b \oplus c a=bc b b b 也必须被表现为 1 y 1 y 2 ⋯ y m 1y_{1}y_{2}\cdots y_{m} 1y1y2ym 这种形式;

  因此 b b b 绝对大于 c c c,所以我们可以直接跳过对 c c c 的讨论。

  受上述条件限制, c c c 有和 b b b 相同位 1 1 1 的充分条件是 a a a 在此位为 0 0 0,所以 b b b 在小于 a a a 的同时,必须还有一位二进制数与 a a a 相反,为了满足这个性质,这里换一种思路。

  直接选择所有 1 y 1 y 2 ⋯ y m 1y_{1}y_{2}\cdots y_{m} 1y1y2ym y 1 y 2 ⋯ y m < x 1 x 2 ⋯ x m y_{1}y_{2}\cdots y_{m} < x_{1}x_{2}\cdots x_{m} y1y2ym<x1x2xm,然后从中剔除不满足性质的元素。

  选择这个集合等价于增加 a a a 去掉前导 1 1 1 的一串二进制数,而去掉不满足性质的元素,等价于减去 2 2 2 的这串二进制数中 1 1 1 的个数次幂。

  举个例子:

  设 a a a 0 b 101011 0\mathrm{b}101011 0b101011 b b b 必大于 0 b 100000 0\mathrm{b}100000 0b100000,因此我们直接选中 [ 0 b 100000 , 0 b 101011 ) [0\mathrm{b}100000,0\mathrm{b}101011) [0b100000,0b101011) 0 b 101011 − 0 b 100000 = 0 b 1011 0\mathrm{b}101011 - 0\mathrm{b}100000 = 0\mathrm{b}1011 0b1010110b100000=0b1011 个元素,而不存在 a a a 该位为 0 0 0 b b b 不为 1 1 1 这种情况的集合为 { 0 b x 1 0 x 2 x 3 } \{0\mathrm{b}x_{1}0x_{2}x_{3}\} { 0bx10x2x3} x i ∈ 0   o r   1 x_{i} \in 0\ or\ 1 xi0 or 1,显然集合元素数量为 2 c o u n t B i t ( 0 b 1011 ) 2^{\mathrm{countBit}(0\mathrm{b}1011)} 2countBit(0b1011),相减后即为我们想要的结果。


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

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 T = in.readInt(), upper = 0;
        int[] Q = new int[T];
        for (int i = 0; i < T; i++)
            upper = max(upper, Q[i] = in.readInt());
        long[] A = new long[upper + 1];
        for (int i = 1; i <= upper; i++) {
    
    
            int b = i - highBit(i);
            A[i] = A[i - 1] + b - (1 << countBit(b)) + 1;
        }
        for (int i = 0; i < T; i++)
            out.println(6 * A[Q[i]]);
        out.flush();
    }

    int highBit(int n) {
    
    
        n |= (n >> 1);
        n |= (n >> 2);
        n |= (n >> 4);
        n |= (n >> 8);
        n |= (n >> 16);
        return n - (n >>> 1);
    }

    int countBit(int n) {
    
    
        n = n - ((n >>> 1) & 0x55555555);
        n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
        n = (n + (n >>> 4)) & 0x0f0f0f0f;
        n += n >>> 8;
        n += n >>> 16;
        return n & 0x3f;
    }

    int max(int a, int b) {
    
     return a > b ? a : b; }

    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()); }
    }
}

组合数学


  根据上述递推,显然有对于每个询问 n i n_{i} ni,都有回答

   ∑ i = 1 n j ( j + 1 − h i g h B i t ( j ) − 2 c o u n t B i t ( j ) − 1 ) \displaystyle\sum_{i = 1}^{n_{j}} (j + 1 - highBit(j) - 2^{countBit(j) - 1}) i=1nj(j+1highBit(j)2countBit(j)1),即

   n i ( n i + 3 ) 2 − ∑ j = 1 n i ( h i g h B i t ( j ) + 2 c o u n t B i t ( j ) − 1 ) \cfrac{n_{i}(n_i + 3)}{2} - \displaystyle\sum_{j = 1}^{n_{i}} (highBit(j) + 2^{countBit(j) - 1}) 2ni(ni+3)j=1ni(highBit(j)+2countBit(j)1)

  对于每个 ∑ j = 1 n i h i g h B i t ( j ) \displaystyle\sum_{j = 1}^{n_{i}} highBit(j) j=1nihighBit(j),都可以分解为

   ∑ k = 1 ⌊ log ⁡ 2 n i ⌋ ∑ j = 2 k − 1 2 k − 1 h i g h B i t ( j ) + ( n i − n i ⌊ log ⁡ 2 n i ⌋ + 1 ) h i g h B i t ( n i ⌊ log ⁡ 2 n i ⌋ ) \displaystyle\sum_{k = 1}^{\lfloor \log _{2} n_{i} \rfloor}\displaystyle\sum_{j = 2^{k-1}}^{2^{k}-1} highBit(j) + (n_{i} - n_{i}^{\lfloor \log _{2} n_{i} \rfloor} + 1)highBit(n_{i}^{\lfloor \log _{2} n_{i} \rfloor}) k=1log2nij=2k12k1highBit(j)+(ninilog2ni+1)highBit(nilog2ni)

  这个式子过于简单,这里便不再讨论,

  重点在 ∑ j = 1 n i 2 c o u n t B i t ( j ) − 1 = ∑ j = 1 n i 2 c o u n t B i t ( j ) 2 \displaystyle\sum_{j = 1}^{n_{i}} 2^{countBit(j) - 1} = \cfrac{\displaystyle\sum_{j = 1}^{n_{i}} 2^{countBit(j)}}{2} j=1ni2countBit(j)1=2j=1ni2countBit(j) 部分,为了方便讨论,这只讨论分子部分并变换符号,

  于是有公式 ∑ i = 1 n 2 c o u n t B i t ( i ) \displaystyle\sum_{i = 1}^{n} 2^{countBit(i)} i=1n2countBit(i)

   = ∑ i = 1 2 k 2 c o u n t B i t ( i ) + ∑ i = 1 n − 2 k 2 c o u n t B i t ( i + 2 k ) =\displaystyle\sum_{i = 1}^{2^k} 2^{countBit(i)} + \displaystyle\sum_{i = 1}^{n - 2^k} 2^{countBit(i + 2^k)} =i=12k2countBit(i)+i=1n2k2countBit(i+2k),其中 k = ⌊ log ⁡ 2 n i ⌋ k = \lfloor \log _{2} n_i \rfloor k=log2ni

  显然第二项中,每个 i i i 都不大于 2 k 2^k 2k,在 c o u n t B i t countBit countBit 意义下, c o u n t B i t ( i + 2 k ) = c o u n t B i t ( i ) + 1 countBit(i + 2^k) = countBit(i) + 1 countBit(i+2k)=countBit(i)+1

  因此, ∑ i = 1 n 2 c o u n t B i t ( i ) = ∑ i = 1 2 k 2 c o u n t B i t ( i ) + 2 × ∑ i = 1 n − 2 k 2 c o u n t B i t ( i ) \displaystyle\sum_{i = 1}^{n} 2^{countBit(i)} = \displaystyle\sum_{i = 1}^{2^k} 2^{countBit(i)} + 2×\displaystyle\sum_{i = 1}^{n - 2^k} 2^{countBit(i)} i=1n2countBit(i)=i=12k2countBit(i)+2×i=1n2k2countBit(i)

  而对于 n = 2 k n = 2^{k} n=2k k ∈ N k \in N kN 的情况,我们可以对 [ 1 , 2 k − 1 ] [1,2^{k-1}] [1,2k1] 做仿射变换到 [ 1 , 2 k ] [1,2^k] [1,2k],同时映射出结果。

  更详细的说,

  定义正整数数集 N k ∈ [ 1 , 2 k ] N_{k} \in [1,2^{k}] Nk[1,2k],常量 A k = ∑ i = 1 N k 2 c o u n t B i t ( i ) A_{k} = \displaystyle\sum_{i = 1}^{N^{k}} 2^{countBit(i)} Ak=i=1Nk2countBit(i)

   N 0 = { 1 } N_{0} = \{1\} N0={ 1} N k + 1 N_{k+1} Nk+1 中的元素由 N k N_{k} Nk k = 2 k = 2 k=2 b = { 0 , 1 } b=\{0,1\} b={ 0,1} 的两次仿射变换而来,特殊的, 2 k 2^k 2k 映射为 1 1 1

在这里插入图片描述
   N k N^{k} Nk 中没有重复元素,同时仿射变换 b = { 0 , 1 } b=\{0,1\} b={ 0,1} 的两种情况的奇偶性是不相同的,因此映射得到的 N k + 1 N^{k + 1} Nk+1 集合是完全的。

   2 N k + 0 2N^{k} + 0 2Nk+0 显然等于 A k A_{k} Ak,而 2 N k + 1 2N^{k} + 1 2Nk+1 时,每次特殊映射的 2 k 2^{k} 2k bit 数为 1 1 1 2 k 2^{k} 2k 映射为 1 1 1 后 bit 数同为 1 1 1,因此 2 N k + 1 = 2 ( A k − 1 ) + 1 2N^{k} + 1= 2(A_{k} - 1)+1 2Nk+1=2(Ak1)+1

  整理可得 A k = 3 A k − 1 − 1 = ∑ i = 1 2 k 2 c o u n t B i t ( i ) = { 1 k = 0 3 ∑ i = 1 2 k − 1 2 c o u n t B i t ( i ) − 1 A_{k} = 3A_{k-1} -1 = \displaystyle\sum_{i = 1}^{2^{k}} 2^{countBit(i)}=\left\{ \begin{array}{l|r} 1&k=0\\ 3\displaystyle\sum_{i = 1}^{2^{k - 1}} 2^{countBit(i)} -1 \end{array} \right. Ak=3Ak11=i=12k2countBit(i)=13i=12k12countBit(i)1k=0

  至此,我们整理出一套可以在 O ( log ⁡ n i ) O(\log n_{i}) O(logni) 内计算出 ∑ i = 1 n i ( i + 1 − h i g h B i t ( i ) − 2 c o u n t B i t ( i ) − 1 ) \displaystyle\sum_{i = 1}^{n_{i}} (i + 1 - highBit(i) - 2^{countBit(i) - 1}) i=1ni(i+1highBit(i)2countBit(i)1) 的公式。


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

public class Main {
    
    

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

    long[] highB = new long[0x20];
    long[] countB = new long[0x20];

    void run() {
    
    
        InputReader in = new InputReader(System.in);
        PrintWriter out = new PrintWriter(System.out);
        int T = in.readInt(), n, m, k;
        countB[0] = 1;
        for (int i = 1; i < 0x20; i++) {
    
    
            highB[i] = (highB[i - 1] << 2) | 1;
            countB[i] = countB[i - 1] * 3 - 1;
        }
        while (T-- > 0) {
    
    
            n = in.readInt();
            k = floorLog2(n);
            m = n - (1 << k);
            out.println(6 * (
                (n + 3L) * n / 2 -
                (calcCountBit(n)) -
                highB[k] - (m + 1L) * (1 << k)
            ));
        }
        out.flush();
    }

    long calcCountBit(int n) {
    
    
        if (n == 0) return 0;
        int m = highBit(n);
        long ans = countB[floorLog2(m)];
        if (n != m)
            ans += calcCountBit(n - m) << 1;
        return ans;
    }

    int[] FLOOR_LOG2_TABLE = {
    
     0, 0, 1, 26, 2, 23, 27, 32, 3, 16, 24, 30, 28, 11, 33, 13, 4, 7, 17, 35, 25, 22, 31, 15, 29, 10, 12, 6, 34, 21, 14, 9, 5, 20, 8, 19, 18 };

    int highBit(int n) {
    
    
        n |= (n >> 1);
        n |= (n >> 2);
        n |= (n >> 4);
        n |= (n >> 8);
        n |= (n >> 16);
        return n - (n >>> 1);
    }

    int floorLog2(int a) {
    
     return FLOOR_LOG2_TABLE[highBit(a) % 37]; }

    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()); }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_43449564/article/details/120803410
今日推荐