第十四届蓝桥杯大赛软件赛省赛(Java 大学A组)

蓝桥杯 2023年省赛真题
Java 大学A组

试题 A: 特殊日期
试题 B: 与或异或
试题 C: 平均
试题 D: 棋盘
试题 E: 互质数的个数
试题 F: 阶乘的和
试题 G: 小蓝的旅行计划
试题 H: 太阳
试题  I: 高塔
试题 J: 反异或 01 串


请添加图片描述
那么根据这个夹逼定理我们容易得出湖北经济学院趋近于武大华科。


试题 A: 特殊日期

本题总分5

【问题描述】

  记一个日期为

y

y

small yy

yy

m

m

small mm

mm

d

d

small dd

dd 日,统计从

2000

small 2000

2000

1

small 1

1

1

small 1

1 日到

2000000

small 2000000

2000000

1

small 1

1

1

small 1

1 日,有多少个日期满足年份

y

y

small yy

yy 是月份

m

m

small mm

mm 的倍数,同时也是

d

d

small dd

dd 的倍数。

【答案提交】

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


35813063


朴素解法


  考虑到以日为单位计量,时间段的阶并不大,因此直接使用java.time.LocalDate模拟日期变化,判断然后累加,程序在笔者

P

C

rm PC

PC 运行

15

15

15 秒后得到了答案。

import java.time.LocalDate;

public class Main {

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

    LocalDate start = LocalDate.of(2000, 1, 1);
    LocalDate end = LocalDate.of(2000000, 1, 1);

    void run() {
        long ans = 0;
        for (LocalDate i = start; i.compareTo(end) <= 0; i = i.plusDays(1))
            if (i.getYear() % i.getMonthValue() == 0 
                    && i.getYear() % i.getDayOfMonth() == 0) ++ans;
        System.out.println(ans);
    }
}

倍数法


  设函数

f

y

(

x

)

=

{

1

x


y

0

o

t

h

e

r

w

i

s

e

f_y(x) = begin{cases} 1 & x,|,y\ 0 & rm otherwise end{cases}

fy(x)={10xyotherwise,对于正整数

y

y

y,记

d

y

d_y

dy

x

=

1

31

f

y

(

x

)

sum_{x=1}^{31}f_y(x)

x=131fy(x)

m

y

m_y

my

x

=

1

12

f

y

(

x

)

sum_{x=1}^{12} f_y(x)

x=112fy(x),则在不考虑日期是否合法的情况下,答案为

y

=

2000

2000000

1

d

y

m

y

+

1

sum_{y=2000}^{2000000 - 1} operatorname{d}_yoperatorname{m}_y+1

y=200020000001dymy+1。因此我们用倍数法在线性时间复杂度意义下求出

d

d

d

m

m

m,将答案拆分成若干合法部分累加,最后得到正确答案。

import java.util.function.IntPredicate;
import java.util.stream.IntStream;

public class Main {

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

    void run() {
        System.out.println(
                calc(IntStream.rangeClosed(1, 12), IntStream.range(1, 29), y  -> true)
                + calc(IntStream.of(2), IntStream.of(29), y -> y % 100 == 0 ? (y % 400 == 0) : (y % 4 == 0))
                + calc(IntStream.iterate(1, m -> m == 1 ? 3 : m + 1).limit(11), IntStream.rangeClosed(29, 30), y  -> true)
                + calc(IntStream.of(1, 3, 5, 7, 8, 10, 12), IntStream.of(31), y  -> true)
                + 1
        );
    }

    final int start = 2000, end = 2000000;

    int calc(IntStream m, IntStream d, IntPredicate yf) {
        int[] my = new int[end + 1];
        int[] dy = new int[end + 1];
        m.forEach(
                a -> {
                    for (int i = 1; i * a <= end; ++i) ++my[i * a];
                }
        );
        d.forEach(
                a -> {
                    for (int i = 1; i * a <= end; ++i) ++dy[i * a];
                }
        );
        return IntStream.range(start, end).filter(yf).map(
                a -> my[a] * dy[a]
        ).sum();
    }
}

  帅是帅,但放在竞赛中性价比极低,编程多耗费时间都够你针对朴素解法的程序给出好几个测试用例了。


试题 B: 与或异或

本题总分5

【问题描述】

  小蓝有一张门电路的逻辑图,如下图所示

请添加图片描述

  图中每个三角形代表着一种门电路,可能是与门、或门、异或门中的任何一种,它接受上一层中的两个圆形中的数据作为输入,产生一个输出值输出到下一级(如图中箭头所示)。图中圆形表示的是暂存的输出结果,取值只可能是

0

small 0

0

1

small 1

1,为了便于表示我们用

a

r

r

[

i

]

[

j

]

small arr[i][ j]

arr[i][j] 表示第

i

(

0

i

4

)

small i(0 ≤ i ≤ 4)

i(0i4) 行第

j

(

0

j

i

)

small j(0 ≤ j ≤ i)

j(0ji) 个圆形的值。其中

a

r

r

[

0

]

=

(

I

n

[

0

]

,

I

n

[

1

]

,

I

n

[

2

]

,

I

n

[

3

]

,

I

n

[

4

]

)

small arr[0] = (In[0], In[1], In[2], In[3], In[4])

arr[0]=(In[0],In[1],In[2],In[3],In[4]) 表示的是输入数据,对于某个

a

r

r

[

i

]

[

j

]

(

i

0

)

small arr[i][ j](i ≤ 0)

arr[i][j](i0),计算方式为

a

r

r

[

i

]

[

j

]

=

a

r

r

[

i

1

]

[

j

]

 

o

p

 

a

r

r

[

i

1

]

[

j

+

1

]

small arr[i][ j] = arr[i − 1][ j] op arr[i − 1][ j + 1]

arr[i][j]=arr[i1][j] op arr[i1][j+1],其中

o

p

small op

op 表示的是将

a

r

r

[

i

1

]

[

j

]

a

r

r

[

i

1

]

[

j

+

1

]

small arr[i − 1][ j]、arr[i − 1][ j + 1]

arr[i1][j]arr[i1][j+1] 作为输入,将

a

r

r

[

i

]

[

j

]

small arr[i][ j]

arr[i][j] 作为输出的那个门电路,与门、或门、异或门分别对应于按位与

(

&

)

small (&)

(&)、按位或

(


)

small (:|:)

()、按位异或

(

small (,

(^

)

small ,)

) 运算符。

  现在已知输入为

I

n

[

0

]

=

1

,

I

n

[

1

]

=

0

,

I

n

[

2

]

=

1

,

I

n

[

3

]

=

0

,

I

n

[

4

]

=

1

small In[0] = 1, In[1] = 0, In[2] = 1, In[3] = 0, In[4] = 1

In[0]=1,In[1]=0,In[2]=1,In[3]=0,In[4]=1,小蓝想要使得最终的输出

O

u

t

small Out

Out 的值为

1

small 1

1,请问一共有多少种不同的门电路组合方式?其中上图中显示的就是一种合法的方式。

【答案提交】

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


30528


深度优先搜索


  暴搜杯!我的青春回来了,开搜!

public class Main {

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

    int[][] cir = new int[6][6];

    int ans = 0, target = 1;

    void run() {
        cir[5] = new int[]{ 0, 1, 0, 1, 0, 1 };
        dfs(1, 5);
        System.out.println(ans);
    }

    void dfs(int i, int n) {
        if (n <= 1) {
            if (cir[n][i] == target) ++ans;
        } else {
            if (i < n) {
                cir[n - 1][i] = cir[n][i] & cir[n][i + 1];
                dfs(i + 1, n);
                cir[n - 1][i] = cir[n][i] | cir[n][i + 1];
                dfs(i + 1, n);
                cir[n - 1][i] = cir[n][i] ^ cir[n][i + 1];
                dfs(i + 1, n);
            } else {
                dfs(1, n - 1);
            }
        }
    }
}

试题 C: 平均

时间限制3.0s 内存限制512.0MB 本题总分10

【问题描述】

  有一个长度为

n

small n

n 的数组(

n

small n

n

10

small 10

10 的倍数),每个数

a

i

small a_i

ai 都是区间

[

0

,

9

]

small [0, 9]

[0,9] 中的整数。小明发现数组里每种数出现的次数不太平均,而更改第

i

small i

i 个数的代价为

b

i

small b_i

bi,他想更改若干个数的值使得这

10

small 10

10 种数出现的次数相等(都等于

n

10

small frac n{10}

10n ),请问代价和最少为多少。

【输入格式】

  输入的第一行包含一个正整数

n

small n

n

  接下来

n

small n

n 行,第

i

small i

i 行包含两个整数

a

i

,

b

i

small a_i, b_i

ai,bi,用一个空格分隔。

【输出格式】

  输出一行包含一个正整数表示答案。

【样例输入】

10
1 1
1 2
1 3
2 4
2 5
2 6
3 7
3 8
3 9
4 10

【样例输出】

27

【样例说明】

  只更改第

1

,

2

,

4

,

5

,

7

,

8

small 1, 2, 4, 5, 7, 8

1,2,4,5,7,8 个数,需要花费代价

1

+

2

+

4

+

5

+

7

+

8

=

27

small 1 + 2 + 4 + 5 + 7 + 8 = 27

1+2+4+5+7+8=27

【评测用例规模与约定】

  对于

20

%

small 20%

20% 的评测用例,

n

1000

small n ≤ 1000

n1000
  对于所有评测用例,

n

100000

,

0

<

b

i

2

×

1

0

5

small n ≤ 100000, 0 < bi ≤ 2 × 10^5

n100000,0<bi2×105


贪心


  记

[

0

,

9

]

[0, 9]

[0,9] 中的整数

x

x

x 修改为整数

y

y

y 的操作为

x

c

y

xxmapsto{c}y

xc

y,即花费代价

c

c

c

x

x

x 修改为

y

y

y,如果一个方案中同时出现

x

c

1

y

y

c

2

z

xxmapsto{c_1}y、yxmapsto{c_2}z

xc1

yyc2

z,那么使用

x

c

1

z

xxmapsto{c_1}z

xc1

z 替换掉这两个操作就能使总代价减小

c

2

c_2

c2,即原方案一定非最优。容易归纳出最优方案中一定不存在长度大于

1

1

1 的路径(将

x

y

y

z

xxmapsto{}y、yxmapsto{}z

x

yy

z 看作长度为

2

2

2 的路径),那么方案要满足题意只能取每个整数超出目标出现次数

n

10

frac n{10}

10n 的部分,在这个基础上最小代价和就是每个整数取超出部分个最小代价之和。

import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;

public class Main {

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

    int[] buffer = new int[200000], cnt = new int[10];

    void run() {
        int n = nextInt(), m = n / 10;
        long ans = 0;
        for (int i = 0; i < n; ++i) {
            buffer[i] = nextInt();
            buffer[i] |= nextInt() << 4;
        }
        Arrays.sort(buffer, 0, n);
        for (int i = n - 1; i >= 0; --i)
            if (++cnt[buffer[i] & 0xf] > m)
                ans += buffer[i] >> 4;
        System.out.println(ans);
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int) in.nval;
    }
}

  实现上可以将

a

i

,

b

i

a_i, b_i

ai,bi 用一个整形表示,以减少排序的开销,也可以按

a

i

a_i

ai

b

i

b_i

bi 分类再依次排序。


试题 D: 棋盘

时间限制3.0s 内存限制512.0MB 本题总分10

【问题描述】

  小蓝拥有

n

×

n

small n × n

n×n 大小的棋盘,一开始棋盘上全都是白子。小蓝进行了

m

small m

m 次操作,每次操作会将棋盘上某个范围内的所有棋子的颜色取反 (也就是白色棋子变为黑色,黑色棋子变为白色)。请输出所有操作做完后棋盘上每个棋子的颜色。

【输入格式】

  输入的第一行包含两个整数

n

,

m

small n, m

n,m,用一个空格分隔,表示棋盘大小与操作数。

  接下来

m

m

m 行每行包含四个整数

x

1

,

y

1

,

x

2

,

y

2

small x_1, y_1, x_2, y_2

x1,y1,x2,y2,相邻整数之间使用一个空格分隔,表示将在

x

1

small x_1

x1

x

2

small x_2

x2 行和

y

1

small y_1

y1

y

2

small y_2

y2 列中的棋子颜色取反。

【输出格式】

  输出

n

small n

n 行,每行

n

small n

n

0

small 0

0

1

small 1

1 表示该位置棋子的颜色。如果是白色则输出

0

small 0

0,否则输出

1

small 1

1

【样例输入】

3 3
1 1 2 2
2 2 3 3
1 1 3 3

【样例输出】

001
010
100

【评测用例规模与约定】

  对于

30

%

small 30%

30% 的评测用例,

n

 

m

500

small n m ≤ 500

n m500
  对于所有评测用例,

1

n

,

m

2000

1

x

1

x

2

n

1

y

1

y

2

m

small 1 ≤ n, m ≤ 2000,1 ≤ x_1 ≤ x_2 ≤ n,1 ≤ y_1 ≤ y_2 ≤ m

1n,m20001x1x2n1y1y2m


二维差分


  如果我们用数字的奇偶性来表示黑白棋,用

n

×

n

ntimes n

n×n 大小且元素全为

0

0

0 的矩阵来表示初始棋盘,容易发现对棋盘

x

1

small x_1

x1

x

2

small x_2

x2 行和

y

1

small y_1

y1

y

2

small y_2

y2 列中的棋子颜色取反的操作与对矩阵

x

1

small x_1

x1

x

2

small x_2

x2 行和

y

1

small y_1

y1

y

2

small y_2

y2 列中的元素加

1

1

1 的操作等价,我们只需要所有操作结束后的棋盘状态,很典型的二维差分问题。
  实现上用到了异或运算在相加上不进位与

0

0

0

1

1

1

A

S

C

I

I

rm ASCII

ASCII 相邻的特性。

import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class Main {

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

    byte[][] low = new byte[2002][2002];

    void run() {
        PrintWriter out = new PrintWriter(System.out);
        int n = nextInt(), m = nextInt();
        for (int i = 0; i < m; ++i) {
            int x = nextInt(), y = nextInt();
            int k = nextInt(), g = nextInt();
            low[x][y] ^= 1;
            low[x][g + 1] ^= 1;
            low[k + 1][y] ^= 1;
            low[k + 1][g + 1] ^= 1;
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                low[i][j] ^= low[i - 1][j] ^ low[i][j - 1] ^ low[i - 1][j - 1];
                out.write('0' ^ low[i][j]);
            }
            out.write('n');
        }
        out.flush();
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int) in.nval;
    }
}

试题 E: 互质数的个数

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

【问题描述】

  给定

a

,

b

small a, b

a,b,求

1

x

<

a

b

small 1 ≤ x < a^b

1x<ab 中有多少个

x

small x

x

a

b

small a^b

ab 互质。由于答案可能很大,你只需要输出答案对

998244353

small 998244353

998244353 取模的结果。

【输入格式】

  输入一行包含两个整数分别表示

a

,

b

small a, b

a,b,用一个空格分隔。

【输出格式】

  输出一行包含一个整数表示答案。

【样例输入 1】

2 5

【样例输出 1】

16

【样例输入 2】

12 7

【样例输出 2】

11943936

【评测用例规模与约定】

  对于

30

%

small 30%

30% 的评测用例,

a

b

1

0

6

small a^b ≤ 10^6

ab106
  对于

70

%

small 70%

70% 的评测用例,

a

1

0

6

b

1

0

9

small a ≤ 10^6,b ≤ 10^9

a106b109
  对于所有评测用例,

1

a

1

0

9

1

b

1

0

18

small 1 ≤ a ≤ 10^9,1 ≤ b ≤ 10^{18}

1a1091b1018


欧拉函数


  欧拉函数是积性函数,其形式为

φ

(

n

)

varphi(n)

φ(n),表示小于等于

n

n

n 且与

n

n

n 互质的正整数的个数,因此当

a

b

=

1

a^b =1

ab=1 时答案为

0

0

0

φ

(

1

)

=

1

varphi(1)=1

φ(1)=1),其他情况答案为

φ

(

a

b

)

varphi(a^b)

φ(ab)
  依算术基本定理,将

a

b

a^b

ab 分解为

(

p

1

α

1

p

2

α

2

p

s

α

s

)

b

=

i

=

1

s

p

i

b


α

i

(p_1^{alpha_1}p_2^{alpha_2}cdots p_s^{alpha_s})^b=prod_{i=1}^s p_i^{b,cdot,alpha_i}

(p1α1p2α2psαs)b=i=1spibαi,因为

φ

varphi

φ 是积性函数,有:

φ

(

a

b

)

=

i

=

1

s

φ

(

p

i

b


α

i

)

=

i

=

1

s

p

i

b


α

i

1

(

p

i

1

)

=

i

=

1

s

p

i

b


α

i

(

1

1

p

i

)

=

i

=

1

s

p

i

b


α

i

i

=

1

s

(

1

1

p

i

)

=

a

b

i

=

1

s

(

1

1

p

i

)

begin{split} varphi(a^b) &=prod_{i=1}^svarphi(p_i^{b,cdot,alpha_i})\ &=prod_{i=1}^sp_i^{b,cdot,alpha_i-1}(p_i-1)\ &=prod_{i=1}^sp_i^{b,cdot,alpha_i}(1-frac 1{p_i})\ &=prod_{i=1}^s p_i^{b,cdot,alpha_i}prod_{i=1}^s(1-frac 1{p_i})\ &=a^bprod_{i=1}^s(1-frac 1{p_i}) end{split}

φ(ab)=i=1sφ(pibαi)=i=1spibαi1(pi1)=i=1spibαi(1pi1)=i=1spibαii=1s(1pi1)=abi=1s(1pi1)  右连乘式可以通过

a

a

a 质因数分解得出,算法复杂度为

O

(

α

+

a

)

O(alpha+sqrt a)

O(α+a

),其中

O

(

α

)

O(alpha)

O(α) 为计算逆元均摊复杂度。

import java.util.Scanner;

public class Main {

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

    final int mod = 998244353;

    long qpow(long a, long b) {
        long pow = 1;
        for (; b > 0; b >>= 1) {
            if ((b & 1) == 1) pow = pow * a % mod;
            a = a * a % mod;
        }
        return pow;
    }

    void run() {
        Scanner in = new Scanner(System.in);
        int a = in.nextInt();
        if (a == 1) {
            System.out.print("0");
            return;
        }
        long ans = qpow(a, in.nextLong());
        for (int p = 2, root = (int) Math.sqrt(a + 0.5); p <= root; ++p)
            if (a % p == 0){
                ans = ans * qpow(p, mod - 2) % mod * (p - 1) % mod;
                while (a % p == 0) a /= p;
            }
        if (a > 1) ans = ans * qpow(a, mod - 2) % mod * (a - 1) % mod;
        System.out.print(ans);
    }
}

试题 F: 阶乘的和

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

【问题描述】

  给定

n

small n

n 个数

A

i

small A_i

Ai,问能满足

m

!

small m!

m!

i

=

1

n

(

A

i

!

)

small sum_{i=1}^n(A_i!)

i=1n(Ai!) 的因数的最大的

m

small m

m 是多少。其中

m

!

small m!

m! 表示

m

small m

m 的阶乘,即

1

×

2

×

3

×

×

m

small 1 × 2 × 3 × · · · × m

1×2×3×⋅⋅⋅×m

【输入格式】

  输入的第一行包含一个整数

n

small n

n

  第二行包含

n

small n

n 个整数,分别表示

A

i

small A_i

Ai,相邻整数之间使用一个空格分隔。

【输出格式】

  输出一行包含一个整数表示答案。

【样例输入】

3
2 2 2

【样例输出】

3

【评测用例规模与约定】

  对于

40

%

small 40%

40% 的评测用例,

n

5000

small n ≤ 5000

n5000
  对于所有评测用例,

1

n

1

0

5

 

1

A

i

1

0

9

small 1 ≤ n ≤ 10^5 1 ≤ A_i ≤ 10^9

1n105 1Ai109


贪心


  如果我们尽可能的合并较小的阶乘,直至无法合并,如

2

!

+

2

!

+

2

!

=

3

×

2

!

=

3

!

2!+2!+2!=3times2!=3!

2!+2!+2!=3×2!=3!,我们取

3

3

3

m

m

m 一定最优,那么这种策略在任何情况都能受用吗?
  首先我们将

A

1

,

A

2

,


,

A

n

A_1, A_2,cdots,A_n

A1,A2,,An 按升序重排,即我们可以一般性假设

i

<

j

,

A

i

A

j

i < j, A_i leq A_j

i<j,AiAj,当

A

s

A_s

As 无法被合并时,即

count

(

A

s

)

<

A

s

+

1

operatorname{count}(A_s) < A_s + 1

count(As)<As+1

i

=

1

n

A

i

=

A

s

!

i

=

s

n

A

i

!

A

s

!

sum_{i=1}^nA_i=A_s!sum_{i=s}^ncfrac{A_i!}{A_s!}

i=1nAi=As!i=snAs!Ai!,此时我们取

A

s

A_s

As

m

m

m 一定合法,尝试判断

(

A

s

+

1

)

!

(A_s+1)!

(As+1)! 是否为

i

=

1

n

A

i

sum_{i=1}^nA_i

i=1nAi 的因数,等价于

A

s

+

1

A_s +1

As+1 是否为

i

=

s

n

A

i

!

A

s

!

sum_{i=s}^ncfrac{A_i!}{A_s!}

i=snAs!Ai! 的因数。
  记

g

g

g 为第一个值为

A

S

+

1

A_S +1

AS+1 的位置,有

A

s

+

1

 

 

(

A

s

+

1

)

i

=

s

n

A

i

!

(

A

s

+

1

)

!

A_s+1 | (A_s+1)sum_{i=s}^ncfrac{A_i!}{(A_s+1)!}

As+1  (As+1)i=sn(As+1)!Ai!,而

count

(

A

s

)

<

A

s

+

1

operatorname{count}(A_s) < A_s + 1

count(As)<As+1

i

=

s

g

1

A

i

!

(

A

s

)

!

=

count

(

A

s

)

∤

 

A

s

+

1

sum_{i=s}^{g - 1}cfrac{A_i!}{(A_s)!} = operatorname{count}(A_s) not| A_s+1

i=sg1(As)!Ai!=count(As) As+1,因此

A

s

+

1

∤

 

i

=

s

n

A

i

!

A

s

!

A_s +1 not| sum_{i=s}^ncfrac{A_i!}{A_s!}

As+1 i=snAs!Ai! ,故

A

s

A_s

As 最优。

import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;

public class Main {

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

    void run() {
        int n = nextInt();
        int[] A = new int[n];
        for (int i = 0; i < n; ++i)
            A[i] = nextInt();
        Arrays.sort(A);
        int m = A[0], k = 1;
        for (int i = 1; i < n; ++i)
            if (m == A[i]) ++k;
            else {
                while (k > 0 && k % (m + 1) == 0) {
                    k /= ++m;
                    if (m == A[i]) {
                        ++k;
                        break;
                    }
                }
            }
        while (k > 0 && k % (m + 1) == 0) k /= ++m;
        System.out.print(m);
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int) in.nval;
    }
}

试题 G: 小蓝的旅行计划

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

【问题描述】

  小蓝正计划进行一次漫长的旅行。小蓝计划开车完成这次旅行。显然他在途中需要加油,否则可能无法完成这次旅行。

  小蓝要依次经过

n

small n

n 个地点,其中从第

i

1

small i − 1

i1 个地点到达第 i 个地点需要消耗

D

i

s

i

small Dis_i

Disi 升油。小蓝经过的每个地点都有一个加油站,但每个加油站的规定也不同。在第

i

small i

i 个加油站加

1

small 1

1 升油需要

C

o

s

t

i

small Cost_i

Costi 的费用,且在这个加油站最多只能加

L

i

m

i

small Lim_i

Limi升油。

  小蓝的车的油箱也有容量限制,他的车上最多只能装载

m

small m

m 升油。

  一开始小蓝的油箱是满的,请问小蓝需要准备多少钱才能顺利完成他的旅行计划。如果小蓝按给定条件无论准备多少钱都不能完成他的旅行计划,请输出

1

small −1

1

【输入格式】

  输入的第一行包含两个整数

n

m

small n m

nm,用一个空格分隔。

  接下来

n

small n

n 行每行包含

3

small 3

3 个整数

D

i

s

i

 

C

o

s

t

i

 

L

i

m

i

small Dis_i Cost_i Lim_i

Disi Costi Limi,相邻整数之间使用一个空格分隔。

【输出格式】

  输出一行包含一个整数表示答案。

【样例输入】

4 5
2 9 2
4 5 6
3 2 2
4 1 3

【样例输出】

38

【评测用例规模与约定】

  对于

30

%

small 30%

30% 的评测用例,

n

 

D

i

s

i

 

C

o

s

t

i

 

L

i

m

i

 

m

300

small n Dis_i Cost_i Lim_i m ≤ 300

n Disi Costi Limi m300
  对于

60

%

small 60%

60% 的评测用例,

n

 

D

i

s

i

 

C

o

s

t

i

 

L

i

m

i

 

m

5000

small n Dis_i Cost_i Lim_i m ≤ 5000

n Disi Costi Limi m5000
  对于所有评测用例,

1

n

2

×

1

0

5

1

D

i

s

i

 

L

i

m

i

 

m

1

0

9

1

C

o

s

t

i

40000

small 1 ≤ n ≤ 2 × 10^5,1 ≤ Dis_i Lim_i m ≤ 10^9,1 ≤ Cost_i ≤ 40000

1n2×1051Disi Limi m1091Costi40000


贪心


  记某个方案在第

i

i

i 个地点的加油站加了

P

i

P_i

Pi 升油,

P

0

=

m

P_0=m

P0=m

D

i

s

0

=

0

Dis_0=0

Dis0=0,记序列

A

A

A

{

a

i

=

P

i

D

i

s

i

}

{a_i=P_i-Dis_{i}}

{ai=PiDisi}

A

A

A 的前缀和

B

B

B

{

b

i

=

j

=

0

i

(

P

j

D

i

s

j

)

}

{b_i=sum_{j=0}^i(P_j-Dis_{j})}

{bi=j=0i(PjDisj)}
  对于题目给定样例,容易找到加油方案

{

m

,

1

,

5

,

2

}

{m, 1, 5, 2}

{m,1,5,2} 花费为

0

×

5

+

1

×

9

+

5

×

5

+

2

×

2

=

38

0times 5+1times 9 + 5times 5 + 2times 2 = 38

0×5+1×9+5×5+2×2=38,代入到

B

B

B 得出

{

4

,

5

,

4

,

0

}

{4, 5, 4, 0}

{4,5,4,0},事实上

B

B

B 对应着方案在相应地点的剩余油量,因此

b

i

∉

[

0

,

m

]

b_inotin[0,m]

bi[0,m]

b

i

<

D

i

s

i

+

1

b_i < Dis_{i+1}

bi<Disi+1 时方案不可达,

b

n

=

0

b_{n}=0

bn=0 时方案一定非最优(在

n

1

n-1

n1 处少加

b

n

b_{n}

bn 的油总花费变低且方案依然合法)。
  无论如何

D

i

s

Dis

Dis 是不变量,我们可以先计算出

D

i

s

-Dis

Dis 的前缀和,于是对于题目给定样例有

C

=

{

2

,

6

,

9

,

13

}

C={-2, -6, -9, -13}

C={2,6,9,13},然后顺序遍历,依次使

B

[

:

i

]

B[:i]

B[:i] 的方案合法,等价于使用

P

[

:

i

1

]

P[:i-1]

P[:i1]

b

i

b_i

bi 非零,对

P

j

P_j

Pj

j

j

j 则贪心的选择

C

o

s

t

j

Cost_j

Costj 最小的,

P

j

P_j

Pj 合法的增量是

min

{

res

j

,

m

max

{

b

x

,

j

x

<

i

}

}

operatorname{min}{operatorname{res}_j, m - operatorname{max}{b_x,jleq x <i}}

min{resj,mmax{bx,jx<i}},其中

res

j

operatorname{res}_j

resj为第

i

i

i 个地点的加油站的剩余油量,而右侧算式是限制这个增量增加后不会有

b

b

b 大于

m

m

m
  下面给出测试用例对应的

B

B

B 的变化过程,没看太懂的读者可以自行推导一下。
  

{

2

,

6

,

9

,

13

}

{-2, -6, -9, -13}

{2,6,9,13}
  

{
  

3
  

,

1

,

4

,
  

8

}

5

×

C

o

s

t

0

{:,3:,, -1, -4, ,,-8,} gets 5 times Cost_0

{3,1,4,8}5×Cost0
  

{
  

4
  

,
  

0
  

,

3

,
  

7

}

1

×

C

o

s

t

1

{:,4:,,:,0:,, -3, ,,-7,} gets 1 times Cost_1

{4,0,3,7}1×Cost1
  

{
  

4
  

,
  

3
  

,
  

0
  

,
  

4

}

3

×

C

o

s

t

2

{:,4:,,:,3:,,:,0:,, ,,-4,} gets 3 times Cost_2

{4,3,0,4}3×Cost2
  

{
  

4
  

,
  

5
  

,
  

4
  

,
    

0
   

}

2

×

C

o

s

t

2

+

2

×

C

o

s

t

3

{:,4:,,:,5:,,:,4:,, ,,:,0:,,} gets 2 times Cost_2 + 2 times Cost_3

{4,5,4,0}2×Cost2+2×Cost3
  证明也很麻烦,就懒得证了,式中

max

{

b

i

}

operatorname{max}{b_i}

max{bi} 和区间加部分则通过线段树完成,最终算法复杂度为

O

(

n

log

n

)

O(nlog n)

O(nlogn)

import java.io.IOException;
import java.io.BufferedReader;
import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.Queue;

public class Main {

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

    int[] cost = new int[200001], lim = new int[200001];

    void run() {
        Queue<Integer> pq = new PriorityQueue<>((a, b) -> cost[a] - cost[b]);
        int n = nextInt(), m = nextInt();
        while (this.m <= n) this.m <<= 1;
        long sum = m, ans = 0;
        for (int i = 1; i <= n; ++i) {
            tree[this.m + i] = sum -= nextInt();
            cost[i] = nextInt();
            lim[i] = nextInt();
        }
        for (int i = n + 1; i < this.m; ++i)
            tree[this.m + i] = -inf;
        tree[this.m] = -inf;
        for (int i = this.m - 1; i > 0; --i)
            tree[i] = Math.max(tree[i << 1], tree[(i << 1) | 1]);
        for (int i = 1; i <= n; ++i) {
            long need = query(i);
            while (need < 0 && pq.size() > 0) {
                int cheap = pq.peek();
                long res = Math.min(m - query(cheap, i - 1), lim[cheap]);
                if (res > -need) res = -need;
                else pq.poll();
                need += res;
                lim[cheap] -= res;
                add(cheap, n, (int) res);
                ans += res * cost[cheap];
            }
            if (need < 0) {
                System.out.print("-1");
                return;
            }
            pq.offer(i);
        }
        System.out.print(ans);
    }

    long[] tree = new long[1 << 19], mark = new long[1 << 19];

    long inf = 0x3f3f3f3f3f3f3f3fl;

    int m = 1;

    void add(int l, int r, int k) {
        for (l += m - 1, r += m + 1; (l ^ r) != 1; l >>= 1, r >>= 1) {
            if (l < m) {
                tree[l] = Math.max(tree[l << 1], tree[(l << 1) | 1]) + mark[l];
                tree[r] = Math.max(tree[r << 1], tree[(r << 1) | 1]) + mark[r];
            }
            if ((l & 1) == 0) {
                tree[l ^ 1] += k;
                mark[l ^ 1] += k;
            }
            if ((r & 1) == 1) {
                tree[r ^ 1] += k;
                mark[r ^ 1] += k;
            }
        }
        for (; l > 0; l >>= 1, r >>= 1) {
            tree[l] = Math.max(tree[l << 1], tree[(l << 1) | 1]) + mark[l];
            tree[r] = Math.max(tree[r << 1], tree[(r << 1) | 1]) + mark[r];
        }
    }

    long query(int i) {
        long elem = tree[i += m];
        while ((i >>= 1) > 0)
            elem += mark[i];
        return elem;
    }

    long query(int l, int r) {
        long maxL = -inf, maxR = -inf;
        for (l += m - 1, r += m + 1; (l ^ r) != 1; l >>= 1, r >>= 1) {
            maxL += mark[l];
            maxR += mark[r];
            if ((l & 1) == 0)
                maxL = Math.max(maxL, tree[l ^ 1]);
            if ((r & 1) == 1)
                maxR = Math.max(maxR, tree[r ^ 1]);
        }
        for (; l > 0; l >>= 1, r >>= 1) {
            maxL += mark[l];
            maxR += mark[r];
        }
        return Math.max(maxL, maxR);
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int) in.nval;
    }
}

试题 H: 太阳

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

【问题描述】

  这天,小蓝在二维坐标系的点

(

X

,

Y

)

small (X, Y)

(X,Y) 上放了一个太阳,看做点光源。

  他拿来了

n

small n

n 条线段,将它们平行于

x

small x

x 轴放置在了坐标系中,第

i

small i

i 条线段的左端点在

x

i

,

y

i

small x_i, y_i

xi,yi,长度为

l

i

small l_i

li。线段之间不会有重合或部分重合的情况(但可能出现端点相交)。小蓝想知道有多少条线段能被太阳照亮(一条线段有长度大于

0

small 0

0 的部分被照亮就算)。

【输入格式】

  输入的第一行包含三个正整数

n

,

X

,

Y

small n, X, Y

n,X,Y,相邻整数之间使用一个空格分隔。

  接下来

n

small n

n 行,第

i

small i

i 行包含三个整数

x

i

,

y

i

,

l

i

small x_i, y_i, l_i

xi,yi,li,相邻整数之间使用一个空格分隔。

【输出格式】

  输出一行包含一个正整数表示答案。

【样例输入】

3 10 2000000
5 3 5
6 2 4
0 1 10

【样例输出】

2

【评测用例规模与约定】

  对于

30

%

small 30%

30% 的评测用例,

n

1000

small n ≤ 1000

n1000
  对于所有评测用例,

1

n

100000

small 1 ≤ n ≤ 100000

1n100000

0

x

i

,

X

1

0

7

small 0 ≤ x_i, X ≤ 10^7

0xi,X107

0

<

y

i

1

0

5

small 0 < y_i ≤ 10^5

0<yi105

0

<

l

i

100

small 0 < l_i ≤ 100

0<li100

1

0

6

<

Y

1

0

7

small 10^6 < Y ≤ 10^7

106<Y107


区间覆盖


  用例数据可可视化表现为:

  请添加图片描述

  然后将线段投射到

x

x

x 轴上:

  请添加图片描述

  可以发现问题等价,且

y

y

y 是恒等于

0

0

0(这里为了方便图示,将光源拉进了一点,线段作了高亮区分),我们直接按

y

y

y 降序重排线段,就变成了区间覆盖问题,魔改下线段树

O

(

n

log

n

)

O(nlog n)

O(nlogn) 秒杀。
  关于线段在

x

x

x 轴上的投射,即线段端点与光源点构成的直线的横截距,将

y

=

0

y = 0

y=0 代入

(

y

Y

)

=

k

(

x

X

)

(y - Y)=k(x - X)

(yY)=k(xX) 可得

x

=

X

Y

k

x =X-cfrac Yk

x=XkY

import java.io.StreamTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;

public class Main {

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

    class MappedLine implements Comparable<MappedLine> {

        int w;

        double x1, x2;

        MappedLine(int w, double x1, double x2) {
            this.w = w;
            this.x1 = x1;
            this.x2 = x2;
        }

        @Override
        public int compareTo(MappedLine line) {
            return Integer.compare(line.w, this.w);
        }
    }

    MappedLine[] lines = new MappedLine[100000];

    double[] itd = new double[200000];

    int m = 0, X, Y;

    void run() {
        int n = nextInt(), ans = 0;
        X = nextInt();
        Y = nextInt();
        for (int i = 0; i < n; ++i) {
            int x = nextInt(), y = nextInt(), l = nextInt();
            double x1 = mapping(x, y), x2 = mapping(x + l, y);
            lines[i] = new MappedLine(y, x1, x2);
            itd[m++] = x1;
            itd[m++] = x2;
        }
        Arrays.sort(itd, 0, m);
        Arrays.sort(lines, 0, n);
        for (int i = 0; i < n; ++i) {
            int l = dti(lines[i].x1);
            int r = dti(lines[i].x2);
            if (!qau(l, r - 1)) ++ans;
        }
        System.out.print(ans);
    }

    double mapping(int x, int y) {
        if (x == X) return X;
        double k = (double) (Y - y) / (X - x);
        return X - Y / k;
    }

    int dti(double d) {
        int l = 0, r = m - 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (itd[mid] < d + 1e-8) l = mid + 1;
            else r = mid;
        }
        return l;
    }

    boolean[] tree = new boolean[1 << 19];

    boolean qau(int l, int r) {  return queryAndUpdate(1, 0, m - 1, l, r); }

    boolean queryAndUpdate(int n, int L, int R, int l, int r) {
        if (tree[n]) return true;
        if (l <= L && r >= R) {
            tree[n] = true;
            return false;
        }
        int mid = (L + R) >> 1;
        boolean full = true;
        if (l <= mid) full &= queryAndUpdate(n << 1, L, mid, l, r);
        if (r > mid) full &= queryAndUpdate((n << 1) | 1, mid + 1, R, l, r);
        tree[n] = tree[n << 1] & tree[(n << 1) | 1];
        return full;
    }

    StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    int nextInt() {
        try {
            in.nextToken();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (int) in.nval;
    }
}

  这类题粪就粪在编码量和变量精度上,不过这里魔改下线段树编码量还算能接受。关于浮点数离散化映射时的精度有许多解决方法,在关系简单时可以用最简分数表示,内存吃紧时可以用最简分数的商表示,这样可以直接使用双等号来判断浮点数是否相同,极大概率下为

t

r

u

e

rm true

true 的两个浮点型都是由两个相同的分子母计算得出的,或者像本节程序一样在离散表查找

d

d

d 时去找

d

+

p

r

e

c

d + rm prec

d+prec

d

+

p

r

e

c

d + rm prec

d+prec 的前驱,

p

r

e

c

rm prec

prec 是精度。
  严格来讲只有第一种做法是正确的,但一般在竞赛中不会去

h

a

c

k

rm hack

hack 精度在

1

e

1e

1e

-

6

6

6 以上的程序,结合性能考量我们一般会选择最后一种,心情到了可以水水离散化。


试题  I: 高塔

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

【问题描述】

  小蓝正在玩一个攀登高塔的游戏。高塔的层数是无限的,但游戏最多只有

n

small n

n 回合。

  小蓝一开始拥有

m

small m

m 点能量,在每个回合都有一个值

A

i

small A_i

Ai 表示小蓝的角色状态。小蓝每回合可以选择消费任意点能量

C

i

small C_i

Ci (最低消费

1

small 1

1 点,没有上限),他在这回合将最多可以向上攀爬

A

i

C

i

small A_icdot C_i

AiCi 层。实际攀爬的层数取决于小蓝自己在这回合的表现,不过最差也会向上爬一层。

  当某回合小蓝的能量点数耗尽,那么在完成这个回合后,游戏结束。

n

small n

n 回合结束后,不管能量还有没有剩余,游戏都会直接结束。

  给出小蓝每回合的

A

i

small A_i

Ai 和自己一开始的能量点数

m

small m

m。小蓝想知道有多少种不同的可能出现的游玩过程。如果小蓝在两种游玩过程中的任一对应回合花费的能量点数不同或该回合结束时所处层数不同,那么这两种游玩过程就被视为不同。

【输入格式】

  输入的第一行包含两个整数

n

,

m

small n, m

n,m,用一个空格分隔。

  第二行包含

n

small n

n 个整数

A

i

small A_i

Ai,相邻整数之间使用一个空格分隔,表示小蓝每回合的状态值。

【输出格式】

  输出一行包含一个整数表示给定条件下不同游玩过程的数量。由于答案可能很大,你只需要输出答案对

998244353

small 998244353

998244353 取模的结果

【样例输入】

9 15
3 2 5 7 1 4 6 8 3

【样例输出】

392149233

【评测用例规模与约定】

  对于

40

%

small 40%

40% 的评测用例,

n

300

m

500

small n ≤ 300,m ≤ 500

n300m500
  对于所有评测用例,

1

n

2

×

1

0

5

n

m

1

0

18

1

A

i

1

0

9

small 1 ≤ n ≤ 2 × 10^5,n ≤ m ≤ 10^{18},1 ≤ A_i ≤ 10^9

1n2×105nm10181Ai109


组合数学


  为了方便讨论,这里先给出一种小蓝在第

n

n

n 回合结束游戏的游玩过程数量的计算方法。依题意的,第

i

i

i 回合小蓝共消耗了

C

i

C_i

Ci,共消耗了

n

i

=

1

n

C

i

m

n leqsum_{i=1}^nC_ileq m

ni=1nCim点能量,第

i

i

i 回合有

A

i

C

i

A_icdot C_i

AiCi 总不同的走法,故方案数为:

c

1

,

c

2

,

,

c

n

>

0

c

1

+

c

2

+

+

c

n

m

i

=

1

n

A

i

C

i

=

c

1

,

c

2

,

,

c

n

>

0

c

1

+

c

2

+

+

c

n

m

i

=

1

n

C

i

i

=

1

n

A

i

.

sum_{substack{c_1,c_2,cdots,c_n > 0\c_1+c_2+cdots+c_nleq m}}prod_{i=1}^nA_iC_i=sum_{substack{c_1,c_2,cdots,c_n > 0\c_1+c_2+cdots+c_nleq m}}prod_{i=1}^nC_icdotprod_{i=1}^nA_i.

c1,c2,,cn>0c1+c2++cnmi=1nAiCi=c1,c2,,cn>0c1+c2++cnmi=1nCii=1nAi.  记

p

(

n

,

m

)

p(n,m)

p(n,m)

c

1

,

c

2

,

,

c

n

>

0

c

1

+

c

2

+

+

c

n

m

i

=

1

n

C

i

sum_{substack{c_1,c_2,cdots,c_n > 0\c_1+c_2+cdots+c_nleq m}}prod_{i=1}^nC_i

c1,c2,,cn>0c1+c2++cnmi=1nCi。因为在小于

n

n

n 的回合结束要耗尽能量,所以整理可以知最终答案为:

p

(

n

,

m

)

i

=

1

n

A

i

+

j

=

1

n

1

(

p

(

j

,

m

)

p

(

j

,

m

1

)

)

i

=

1

j

A

i

.

p(n,m)prod_{i=1}^nA_i+sum_{j=1}^{n-1}(p(j,m) -p(j,m-1))prod_{i=1}^jA_i.

p(n,m)i=1nAi+j=1n1(p(j,m)p(j,m1))i=1jAi.  因此该思路下,

p

(

n

,

m

)

p(n,m)

p(n,m) 求解的复杂度与整体直接关联,考虑

p

p

p 的边界情况,当

n

=

1

n=1

n=1 时,

p

(

1

,

m

)

=

1

+

2

+

+

m

=

m

(

m

+

1

)

2

p(1,m) = 1+2+cdots+m=cfrac {m(m+1)}2

p(1,m)=1+2++m=2m(m+1)。考虑

n

+

1

n+1

n+1 情况,枚举

C

n

+

1

C_{n+1}

Cn+1 可能的取值

1

,

2

,

m

n

1,2,cdots m-n

1,2,mn,则有递推式:

p

(

n

,

m

)

=

{

1

+

2

+

+

m

n

=

1

i

=

1

m

n

+

1

i

p

(

i

1

,

m

i

)

1

<

n

m

.

p(n,m) = begin{cases} 1+2+cdots+m &n=1 \ sum_{i=1}^{m-n+1} ip(i-1,m-i) &1< nleq m end{cases}.

p(n,m)={1+2++mi=1mn+1ip(i1,mi)n=11<nm.  观察到

p

(

1

,

m

)

=

m

(

m

+

1

)

2

=

C

m

+

1

2

p(1,m)=cfrac {m(m+1)}2=C_{m+1}^2

p(1,m)=2m(m+1)=Cm+12,有

p

(

2

,

m

)

=

C

m

2

+

2

C

m

1

2

+

+

(

m

1

)

C

2

2

=

(

C

m

2

+

C

m

1

2

+

+

C

2

2

)

+

(

C

m

1

2

+

C

m

2

2

+

+

C

2

2

)

+

C

2

2

=

C

m

+

1

3

+

C

m

3

+

+

C

3

3

=

C

m

+

2

4

.

begin{split} p(2,m)&=C_{m}^2+2C_{m-1}^2+cdots+(m-1)C_{2}^2\ &=(C_{m}^2+C_{m-1}^2+cdots+C_{2}^2)+(C_{m-1}^2+C_{m-2}^2+cdots+C_{2}^2)+cdots C_{2}^2\ &=C_{m+1}^3+C_{m}^3+cdots+C_{3}^3\ &=C_{m+2}^4 end{split}.

p(2,m)=Cm2+2Cm12++(m1)C22=(Cm2+Cm12++C22)+(Cm12+Cm22++C22)+C22=Cm+13+Cm3++C33=Cm+24.  类似的,可得关系式

p

(

n

,

m

)

=

C

n

+

m

2

n

p(n,m)=C_{n+m}^{2n}

p(n,m)=Cn+m2n,故最终答案为:

C

n

+

m

2

n

i

=

1

n

A

i

+

j

=

1

n

1

(

C

j

+

m

2

j

C

j

+

m

1

2

j

)

i

=

1

j

A

i

.

C_{n+m}^{2n}prod_{i=1}^nA_i+sum_{j=1}^{n-1}(C_{j+m}^{2j}-C_{j+m-1}^{2j})prod_{i=1}^jA_i.

Cn+m2ni=1nAi+j=1n1(Cj+m2jCj+m12j)i=1jAi.  不对

p

p

p 做优化的情况下时间复杂度为

O

(

n

2

log

m

)

O(n^2log m)

O(n2logm),因此

p

p

p 的值还需迭代计算,最终复杂度为

O

(

n

log

m

)

O(nlog m)

O(nlogm)。此外,在杨辉三角中我们也可以看到

p

(

n

,

m

)

p

(

n

1

,

m

1

)

p

(

n

1

,

n

1

)

p(n,m)、p(n-1,m-1)、p(n-1,n-1)

p(n,m)p(n1,m1)p(n1,n1) 恰围成一个矩形。

请添加图片描述

  大概就是这么个感觉,可以直接考虑

p

(

n

,

i

)

p(n,i)

p(n,i)

p

(

n

+

1

,

m

)

,

i

<

n

p(n+1,m),i< n

p(n+1,m),i<n 的贡献,可以发现贡献恰为

i

i

i,但图画出来太丑了,就懒的画了。

import java.util.StringTokenizer;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class Main {

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

    final int mod = 998244353;

    long qpow(long a, int b) {
        long res = 1;
        a %= mod;
        for (; b > 0; b >>= 1) {
            if (b % 2 == 1)
                res = res * a % mod;
            a = a * a % mod;
        }
        return res;
    }

    void run() {
        int n = nextInt();
        long m = nextLong() % mod;
        long A = 1, p1 = 1, p2 = 1, ans = 0;
        for (int i = 1; i <= n; ++i) {
            A = A * nextInt() % mod;
            p1 = p1 * (m + i) % mod * (m - i + 1) % mod * qpow(2 * i, mod - 2) % mod * qpow(2 * i - 1, mod - 2) % mod;
            p2 = p2 * (m - 1 + i) % mod * (m - i) % mod * qpow(2 * i, mod - 2) % mod * qpow(2 * i - 1, mod - 2) % mod;
            if (i == n) p2 = 0;
            ans = (ans + (p1 - p2) * A) % mod;
        }
        System.out.print((ans + mod) % mod);
    }

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    StringTokenizer token;

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

    int nextInt() { return Integer.parseInt(next()); }

    long nextLong() { return Long.parseLong(next()); }
}

  

n

n

n 小两阶就蛮好,但这样

d

p

rm dp

dp 可以骗到不少分,不过要是在比赛时推导出来但迭代写的有

b

u

g

rm bug

bug,半天

d

e

rm de

de 不出来,心态就会挺炸的。


试题 J: 反异或 01 串

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

【问题描述】

  初始有一个空的

01

small 01

01 串,每步操作可以将

0

small 0

0

1

small 1

1 添加在左侧或右侧。也可以对整个串进行反异或操作: 取

s

=

s

r

e

v

(

s

)

small s^′ = s oplus rev(s)

s=srev(s),其中

s

small s

s 是目前的

01

small 01

01 串,

small oplus

表示逐位异或,

r

e

v

(

s

)

small rev(s)

rev(s) 代表将

s

small s

s 翻转,也就是说取中心位置并交换所有对称的两个位置的字符。例如,

r

e

v

(

0101

)

=

1010

 

r

e

v

(

010

)

=

010

 

r

e

v

(

0011

)

=

1100

small rev(0101) = 1010 rev(010) = 010 rev(0011) = 1100

rev(0101)=1010 rev(010)=010 rev(0011)=1100

  反异或操作最多使用一次(可以不用,也可以用一次)。

  给定一个

01

small 01

01

T

small T

T,问最少需要添加多少个

1

small 1

1 才能从一个空

01

small 01

01 串得到

T

small T

T。在本题中

0

small 0

0 可以添加任意个。

【输入格式】

  输入一行包含一个

01

small 01

01 串表示给定的

T

small T

T

【输出格式】

  输出一行包含一个整数,表示需要最少添加多少个

1

small 1

1

【样例输入】

00111011

【样例输出】

3

【评测用例规模与约定】

  对于

20

%

small 20%

20% 的评测用例,

T

10

small |T| ≤ 10

T10
  对于

40

%

small 40%

40% 的评测用例,

T

500

small |T| ≤ 500

T500
  对于

60

%

small 60%

60% 的评测用例,

T

5000

small |T| ≤ 5000

T5000
  对于

80

%

small 80%

80% 的评测用例,

T

1

0

5

small |T| ≤ 10^5

T105
  对于所有评测用例,

1

T

1

0

6

small 1 ≤ |T| ≤ 10^6

1T106,保证

T

small T

T 中仅含

0

small 0

0

1

small 1

1


Manacher


  如果

T

T

T 没有使用反异或操作,那么答案为

T

T

T

1

1

1 的个数

count

1

(

T

)

operatorname{count}_{‘1’}(T)

count‘1’(T)。如果

T

T

T 使用过反异或操作,由于每次添加操作都是发生在两侧,操作后得到的回文串

T

T'

T 回文性不会被破坏且仍能在

T

T

T 中找到。
  于是我们可以考虑将

T

T

T 还原到

T

T'

T,构造出

T

[

0

:


T

/

2

]

T'[0:lceil|,T'|/2rceil]

T[0:T∣/2⌉],右侧添加


T

/

2

lfloor|,T'|/2rfloor

T∣/2

0

0

0 后使用反异或操作,此时答案为

count

1

(

T

)

count

1

(

T

[

0

:


T

/

2

]

)

operatorname{count}_{‘1’}(T) - operatorname{count}_{‘1’}(T'[0:lfloor|,T'|/2rfloor])

count‘1’(T)count‘1’(T[0:T∣/2⌋]),因此找到最大的

count

1

(

T

[

0

:


T

/

2

]

)

operatorname{count}_{‘1’}(T'[0:lfloor|,T'|/2rfloor])

count‘1’(T[0:T∣/2⌋]) 就能使答案最小,算法复杂度和回文串求解相关,于是选用

M

a

n

a

c

h

e

r

rm Manacher

Manacher 算法结合前缀和

O

(

T

)

O(|T|)

O(T) 求解。
  不过需要注意的是在

T

T'

T 的长度为奇数时,

T

T'

T 的中位字符为

1

1

1 时是非法的,因为反异或操作后该位就变成了

0

0

0,这一点可以在实现

M

a

n

a

c

h

e

r

rm Manacher

Manacher 时在

T

T

T 字符间插入

0

0

0 字符,然后只记

0

0

0 字符的回文串有效即可。

import java.io.BufferedInputStream;
import java.io.IOException;

public class Main {

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

    int[] dp = new int[2000010], sum = new int[2000010];

    byte[] T = new byte[2000010];

    void run() {
        int n = 0, ans = 0;
        while (true) {
            T[++n] = '0';
            sum[n] = sum[n - 1];
            T[++n] = read();
            sum[n] = sum[n - 2] + T[n] - '0';
            if ('1' != (T[n] | 1)) break;
        }
        T[n] = 0x7f;
        for (int i = 1, l = 0, r = 0; i < n; ++i) {
            int k = i > r ? 0 : Math.min(dp[l + r - i], r - i);
            while (T[i - k] == T[i + k]) ++k;
            dp[i] = --k;
            if (T[i] == '0')
                ans = Math.max(ans, sum[i + k] - sum[i]);
            if (i + k > r) {
                r = i + k;
                l = i - k;
            }
        }
        System.out.println(sum[n - 2] - ans);
    }

    BufferedInputStream in = new BufferedInputStream(System.in);

    byte read() {
        try {
            return (byte) in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }
}

  但比赛时

M

a

n

a

c

h

e

r

rm Manacher

Manacher 怎么写还真就那个忘了,后缀数组也不会写,胡掐了半个

M

a

n

a

c

h

e

r

rm Manacher

Manacher 上去,拿这个压轴是真离谱啊。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>