蓝桥杯第十二届Java大学生B组省赛真题及题解

试题A: ASC

本题总分: 5分

[问题描述]

已知大写字母A的ASCII码为65,请问大写字母L的ASCII码是多少?

[答案提交]

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

[题解]

方法一:直接计算

'L' - 'A' + 65 即 12 - 1 + 65

方法二:程序打印

System.out.println((int)'L');

[答案]

76

试题B:卡片

本题总分: 5分

[问题描述]

小蓝有很多数字卡片,每张卡片上都是数字0到9。

小蓝准备用这些卡片来拼一些数,他想从1开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。

小蓝想知道自己能从1拼到多少。

例如,当小蓝有30张卡片,其中0到9各3张,则小蓝可以拼出1到10,但是拼11时卡片1已经只有一-张了,不够拼出11。

现在小蓝手里有0到9的卡片各2021张,共20210张,请问小蓝可以从1拼到多少?

提示:建议使用计算机编程解决问题。

[答案提交]

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

[题解]

public class Main {

    public static void main(String[] args) {
        // 卡片 1 肯定是最先耗尽的
        int cnt = 0;
        String str = "";
        int index = 0;
        int num = 1;
        for(;cnt <= 2021;num++) {
            str += num;
            for (;index < str.length(); index++) {
                if (str.charAt(index) == '1') {
                    cnt++;
                }
            }

        }
        // 由于结束循环 num 还会 +1 故实际的 num 应为 num -1
        if (cnt == 2021) System.out.println(num - 1);
        else System.out.println(num - 2);
    }

}

[答案]

3181

试题C:直线

本题总分: 10分

[问题描述]

在平面直角坐标系中,两点可以确定-条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一-条。

给定平面上2x3个整点(x,y)I0≤x<2,0≤y<3,x∈Z,y∈Z),即横坐标是0到1(包含0和1)之间的整数、纵坐标是0到2(包含0和2)之间的整数的点。这些点一共确定了11条不同的直线。

给定平面上20x21个整点(x,y)I0≤x<20,0≤y<21,x∈Z,y∈Z},即横坐标是0到19(包含0和19)之间的整数、纵坐标是0到20(包含0和20)之间的整数的点。请问这些点一共确定了多少条不同的直线。

[答案提交]

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

[题解]

直线可写成点斜式方程 y = k x + b ​ y = kx + b​ y=kx+b 来确定。即 k 和 b 不同,则直线不同。

化简得 k = y 2 − y 1 x 2 − x 1 k = \frac{y_2 - y_1}{x_2 - x_1} k=x2x1y2y1, b = x 2 y 1 − x 1 y 2 x 2 − x 1 b = \frac{x_2y_1 - x_1y_2}{x_2 - x_1} b=x2x1x2y1x1y2


import java.util.*;

public class Main {

    public static void main(String[] args) {
        ArrayList<Integer> nums = new ArrayList<>();
        Set<String> setLine = new HashSet<>();

        for (int x = 0; x < 20; x++) {
            for (int y = 0; y < 21; y++) {
                nums.add(y * 100 + x);
            }
        }

        for (int i = 0; i < nums.size(); i++) {
            int x1 = nums.get(i) % 100;
            int y1 = nums.get(i) / 100;
            for (int j = i + 1; j < nums.size(); j++) {
                int x2 = nums.get(j) % 100;
                int y2 = nums.get(j) / 100;

                int k = y2 - y1;
                int b = x2 * y1 - x1 * y2;
                int down = x2 - x1;
                int remainder = gcd(k, b, down);
                k /= remainder;
                b /= remainder;
                down /= remainder;
                String str;
                // 通过 set 去重时,要注意为字符串添加明确的标识,以防本不同直线,因表示不明确而被去掉
                if (down == 0) {
                    str = "x =" + x1;
                } else if (k == 0) {
                    str = "y =" + y1;
                } else {
                    str = down + "y = " + k + "x + " + b;

                }
                setLine.add(str);
            }
        }
        System.out.println(setLine.size());
    }

    /**
     * 在该题下,不存在 a, b, c 三者中任意两者同时为零的情况
     *
     * @param a
     * @param b
     * @param c
     * @return
     */
    static int gcd(int a, int b, int c) {
        if (a == 0) return gcd(b, c);
        if (b == 0) return gcd(a, c);
        if (c == 0) return gcd(a, b);
        int tmp = gcd(a, b);
        return gcd(tmp, c);
    }

    /**
     * 若 a, b 中存在零,则最大公约数会返回非零数
     *
     * @param a
     * @param b
     * @return
     */
    static int gcd(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);
        if (a < b) {
            int temp = b;
            b = a;
            a = temp;
        }
        return b == 0 ? a : gcd(b, a % b);
    }
    

}

[答案]

40257

试题 D: 货物摆放

本题总分:10分

[问题描述]

小蓝有一个超大的仓库,可以摆放很多货物。
现在,小蓝有n箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。
小蓝希望所有的货物最终摆成一个大的长方体。即在长、宽、高的方向上分别堆 L、W、H 的货物,满足n=LxWxH。
给定 n,请问有多少种堆放货物的方案满足要求。
例如,当n=4时,有以下6种方案:1x1x4、1x2x2、1x4x1、2x1x2、2x2x1、4x1x1.
请问,当n=2021041820210418(注意有16位数字)时,总共有多少种方案?
提示:建议使用计算机编程解决问题。

[答案提交]

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

[题解]


import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        long n = 2021041820210418L;
        // 获取所有因子
        ArrayList<Long> divisor = new ArrayList<>();
        for (long i = 1; i * i <= n; i++) {
            if (n % i == 0) {
                divisor.add(i);
                divisor.add(n / i);
            }
        }
        // 枚举所有可能方案
        int cnt = 0;
        for (int i = 0; i < divisor.size(); i++) {
            for (int j = 0; j < divisor.size(); j++) {
                for (int k = 0; k < divisor.size(); k++) {
                    if (n == divisor.get(i) * divisor.get(j) * divisor.get(k)) cnt++;
                }
            }
        }
        System.out.println(cnt);
    }
}

[答案]

2430

试题 E:路径

本题总分:15 分

[问题描述]

小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。
小蓝的图由2021 个结点组成,依次编号1至2021。
对于两个不同的结点a,b,如果a和b的差的绝对值大于21,则两个结点之间没有边相连;如果a和b的差的绝对值小于等于21,则两个点之间有一条长度为a和b的最小公倍数的无向边相连。
例如:结点1和结点23之间没有边相连;结点3和结点24之间有一条无向边,长度为 24;结点15和结点25之间有一条无向边,长度为75。
请计算,结点1和结点2021之间的最短路径长度是多少。提示:建议使用计算机编程解决问题。

[答案提交]

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

[题解]


import java.util.Arrays;

public class Main {

    static int G[][];
    public static void main(String[] args) {
        // 采用邻接矩阵的形式存储图, 表示不可达
        G = new int[2022][2022];
        for (int[] arr : G) {
            Arrays.fill(arr, Integer.MAX_VALUE);
        }
        for (int i = 1; i <= 2021; i++) {
            for (int j = i + 1; j <= 2021; j++) {
                if (j - i > 21) break;
                G[i][j] = lcm(i, j);
            }
        }
        dijkstra(1);
        System.out.println(dist[2021]);

    }
    static boolean[] vis = new boolean[2022];
    static int[] dist = new int[2022];

    /**
     * 计算最短路径
     * @param s 表示起点
     */
    static void dijkstra(int s) {
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[s] = 0;
        for (int i = 1; i < 2022; i++) {
            int u = -1, min = Integer.MAX_VALUE;
            for (int j = 1; j < 2022; j++) {
                if (vis[j] == false && dist[j] < min) {
                    u = j;
                    min = dist[j];
                }
            }
            // 找不到小于 INF 的 dist[u], 说明剩下顶点和起点不连通
            if (u == -1) return;
            vis[u] = true;
            for (int v = 1; v < 2022; v++) {
                if (vis[v] == false && G[u][v] != Integer.MAX_VALUE && dist[u] + G[u][v] < dist[v]) {
                    dist[v] = dist[u] + G[u][v];
                }
            }
        }
    }

    static int lcm(int a, int b) {
        return a / gcd(a, b) * b;
    }

    static int gcd(int a, int b) {
        if (a < b) {
            int tmp = b;
            b = a;
            a = tmp;
        }
        return b == 0 ? a : gcd(b, a % b);
    }
}

[答案]

10266837

试题 F:时间显示

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

[问题描述]

小蓝要和朋友合作开发一个时间显示的网站。在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从1970年1月1日00:00:00到当前时刻经过的毫秒数。
现在,小蓝要在客户端显示出这个时间。小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
[输入格式]
输入一行包含一个整数,表示时间。
[输出格式]
输出时分秒表示的当前时间,格式形如HH:MM:SS,其中HH表示时,值为0到 23,MM表示分,值为0 到59,SS表示秒,值为0到59。时、分、秒不足两位时补前导 0。

[样例输入1]46800999
[样例输出1]13:00:00

[样例输入 2]1618708103123
[样例输出 2]01:08:23

[评测用例规模与约定]

对于所有评测用例,给定的时间为不超过 1 0 18 10^{18} 1018 的正整数。

[题解]


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

/**
 * 2021 时间显示 简单模拟
 *
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    public static void main(String[] args) throws Exception{
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));
        new Main().go();
        pw.flush();
    }
    
    private long nextLong() throws Exception{
        st.nextToken();
        return (long)st.nval;
    }
    
    // 题目答案
    String ans;
    // 输入的毫秒数
    long n;
    
    private void go() throws Exception{
        n = nextLong();
        getAns();
        pw.println(ans);
    }
    
	private void getAns() {
		n /= 1000;
		int second = (int) (n % 60);
		n /= 60;
		int minute = (int) (n % 60);
		n /= 60;
		int hour = (int) (n % 24);
		if (hour < 10) {
			ans = "0" + hour + ":";
		} else {
			ans = hour + ":";
		}
		if (minute < 10) {
			ans += "0" + minute + ":";
		} else {
			ans += minute + ":";
		}
		if (second < 10) {
			ans += "0" + second;
		} else {
			ans += second;
		}
	}
}

试题 G: 最少砝码

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

[问题描述]

你有一架天平。现在你要设计一套砝码,使得利用这些砝码可以称出任意小于等于N的正整数重量。
那么这套砝码最少需要包含多少个砝码?注意砝码可以放在天平两边。

[输入格式]
输入包含一个正整数N。
[输出格式]
输出一个整数代表答案。
[样例输入]

7

[样例输出]
3
[样例说明]
3 个砝码重量是1、4、6,可以称出1至7的所有重量。

1=1;
2=6-4(天平一边放6,另一边放4);

3=4-1;

4=4;
5=6-1;

6= 6;

7=1+6;
少于3个砝码不可能称出1至7的所有重量。

[评测用例规模与约定]

对于所有评测用例,1≤N≤1000000000。

[题解]

所称重量砝码最少使用数量各砝码重量
111
221、2
321、2
421、3
531、3、5或1、3、6等
631、3、5或1、3、6等
731、3、5或1、3、6等
831、3、5或1、3、6等
931、3、5或1、3、6等
1031、3、6等
1131、3、7等
1231、3、8等
1331、3、9
1441、3、9、10等
………………

通过上述表格可发现:

砝码个数最大可称出重量
11 = 1
24 = 1 + 3
313 = 1 + 3 + 9
…………

由于在原先砝码个数下,所称重量,都可以表示出来,那么只要保证新增的一个砝码比原先所能称出重量的两倍加一,就能称出该砝码个数可称出的最大重量。

最后根据规律列出如下表格:

砝码个数新增砝码重量最大可称出重量
111
234
3913
42740
………………
n3(n - 1) 3 n − 1 2 \frac{3^n - 1}{2} 23n1

不难发现最多可称重量是个等比数列的和


import java.io.*;

/**
 * 2021 最少砝码 等比数列
 *
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    public static void main(String[] args) throws Exception{
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));
        new Main().go();
        pw.flush();
    }

    private long nextLong() throws Exception{
        st.nextToken();
        return (long)st.nval;
    }

    // 题目答案
    int ans;
    // 输入的重量
    long weight;

    private void go() throws Exception{
        weight = nextLong();
        getAns();
        pw.println(ans);
    }

    private void getAns() {
        double tmp = Math.log10(weight * 2 + 1) / Math.log10(3) ;
        ans = (int)tmp;
        // 由于转为整型,会丢失小数部分使得 ans 变小而不满足要求,故需要判断是否有小数,看要不要加一进行补足
        if (tmp - ans > 0) {
            ans ++;
        }
    }
}

试题 H:杨辉三角形

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

[问题描述]

下面的图形是著名的杨辉三角形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
… … …
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
1,1,1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, …
给定一个正整数N,请你输出数列中第一次出现N是在第几个数?
[输入格式]
输入一个整数 N。
[输出格式]
输出一个整数代表答案。
[样例输入]
6
[样例输出]

13

[评测用例规模与约定]

对于 20% 的评测用例,1≤N≤10;
对于所有评测用例,1≤N≤1000000000。

[题解]

杨辉三角的每行对应着二项式定理的展开

通过枚举得到首个对应的组合数 C n m C^m_n Cnm

根据 m 和 n 即可得到相应的位置 n ( n + 1 ) 2 + m + 1 \frac{n(n + 1)} {2} + m + 1 2n(n+1)+m+1


import java.io.*;

/**
 * 2021 杨辉三角形 简单数学
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

    public static void main(String[] args) throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));
        new Main().go();
        pw.flush();
    }

    private long nextLong() throws Exception {
        st.nextToken();
        return (long) st.nval;
    }

    // 题目答案
    long ans;
    // 输入的整数
    long num;

    private void go() throws Exception {
        num = nextLong();
        getAns();
        pw.println(ans);
    }

    private void getAns() {
        // 将 1 特判出去
        if (num == 1) {
            ans = 1;
            return;
        }
        // 枚举所有组合数,直到找到第一个符合条件的数
        // 由于组合数后面会过大,将必然是符合数的最大位置优先列出,避免过多计算导致超时
        ans = num * (num + 1) / 2 + 2;
        // 由于组合数在 C(37, 18) 就大于界限,即 m 值无需继续求,否则可能会导致超时
        // 该循环从 2 开始,由于提前将 C(n, 1) 的位置赋给 ans,所以不会漏算
        for (long i = 2; i <= 18; i++) {
            // 由于组合数是对称的,在中间取到最大值,二分的下界取为 2 * i时,保证能取到以 m = i 可能是答案的最小位置。
            long left =  2 * i, right = num;
            while (left <= right) {
                long mid = left + right >>> 1;
                if (C(mid, i) == num) {
                    ans = Math.min(ans, mid * (mid + 1) / 2 + i + 1);
                    return;
                } else if (C(mid, i) > num) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }

        }

    }

    /**
     * 计算组合数
     *
     * @param n
     * @param m
     * @return
     */
    private long C(long n, long m) {
        long res = 1;
        for (long i = 1; i <= m; i++) {
            res = res * (n - m + i) / i;
            // 计算出的组合数大于要求的值就没必要继续计算,直接返回即可
            if (res > num) break;
        }
        return res;
    }
}

试题I:双向排序

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

[问题描述]

给定序列( a 1 ​ a_1​ a1, a 2 ​ a_2​ a2,…, a n ​ a_n​ an) =(1,2,… ,n),即 a i ​ a_i​ ai=i。
小蓝将对这个序列进行m次操作,每次可能是将 a 1 ​ a_1​ a1, a 2 ​ a_2​ a2,…, a q i ​ a_{q_i}​ aqi。降序排列,或者将 a q i ​ a_{q_i}​ aqi, a q i + 1 ​ a_{q_{i+1}}​ aqi+1,… , a n ​ a_n​ an 升序排列。
请求出操作完成后的序列。

[输入格式]
输入的第一行包含两个整数n,m,分别表示序列的长度和操作次数。
接下来m 行描述对序列的操作,其中第i行包含两个整数 p i p_i pi, q i q_i qi表示操作类型和参数。当 p i p_i pi=0时,表示将 a 1 a_1 a1, a 2 a_2 a2,…, a q i a_{q_i} aqi。降序排列;当 p i p_i pi=1时,表示将 a q i a_{q_i} aqi, a q i + 1 a_{q_{i+1}} aqi+1,… , a n ​ a_n​ an升序排列。
[输出格式]
输出一行,包含n个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
[样例输入]

3 3

0 3

1 2

0 2
[样例输出]

3 1 2
[样例说明]
原数列为(1,2,3)。

第1步后为(3,2,1)。

第2步后为(3,1,2)。
第3步后为(3,1,2)。与第2 步操作后相同,因为前两个数已经是降序了。

[评测用例规模与约定]

对于 30% 的评测用例,n,m≤1000;对于 60% 的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤100000,0≤ p i p_i pi≤1,1≤ q i q_i qi≤n。

[题解]


import java.io.*;

/**
 * 2021 双向排序
 * 由于数据大小为 10^5 需要将时间复杂度控制在 n log n 内才不会超时
 * 这其实是一道发现规律的题
 * 以下区间长度均代表需要变化的区间
 * 规律①:第一个有效操作一定是前缀降序操作,因为该序列本身就是升序排列的
 * 规律②:在一段连续的前缀降序或后缀升序,其有效操作一定是区间长度最大那次操作,其余操作都可看成在其内部操作,即其余操作没有意义
 * 规律③:由②可得,其有效操作,一定是前缀降序、后缀升序交错出现的
 * 规律④:通过三次前缀降序、后缀升序、前缀降序,可发现若是第三次前缀降序的区间长度大于第一次前缀降序区间长度,则前两次操作皆可删除
 * 规律⑤:由④可得,每次有效操作,其区间长度一定是不断缩小的。即我们只要将已经确定的数赋值即可,最后赋完所有值,就表示排序结束
 * 规律④⑤较难得出,可自己将区间长度标出来,写在纸上慢慢分析
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

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

    private int nextInt() throws Exception {
        st.nextToken();
        return (int) st.nval;
    }

    // 输入的 n 序列的长度, m 操作次数, p 操作类型, q 参数
    int n, m;
    int p, q;
    // 序列 a
    int[] a;
    // 实际有效操作, 通过栈的方式记录
    int[][] stk;
    int top;

    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    private void getAns() throws Exception {
        n = nextInt();
        m = nextInt();
        stk = new int[m + 1][2];

        for (int i = 0; i < m; i++) {
            p = nextInt();
            q = nextInt();
            if (p == 0) {
                // 实现规律②
                while (top > 0 && stk[top][0] == 0) q = Math.max(q, stk[top--][1]);
                // 实现规律④
                while (top >= 2 && stk[top - 1][1] <= q) top -= 2;
                stk[++top] = new int[]{p,q};
            } else if (top > 0) {   // 实现规律①
                // 实现规律②
                while (top > 0 && stk[top][0] == 1) q = Math.min(q, stk[top--][1]);
                // 实现规律④
                while (top >= 2 && stk[top - 1][1] >= q) top -= 2;
                stk[++top] = new int[]{p,q};
            }

        }

        // 实现规律⑤ 填充序列 a 得到答案
        a = new int[n + 1];
        int l = 1, r = n, k = n;
        for (int i = 1; i <= top; i++) {
            if (stk[i][0] == 0)
                while (r > stk[i][1] && l <= r) a[r--] = k--;
            else
                while (l < stk[i][1] && l <= r) a[l++] = k--;
            if (l > r) break;
        }
        // 将最后未填充的数填充进去
        // top 是偶数,说明最后一次操作是后缀升序操作,只要将最后一次需要填入的数,按需要的排序填入即可
        if (top % 2 == 0)
            while (l <= r) a[r--] = k--;
        else
            while (l <= r) a[l++] = k--;

        // 打印答案
        for (int i = 1; i <= n; i++) {
            pw.print(a[i] + " ");
        }
    }
}

试题 J: 括号序列

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

[问题描述]

给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列(((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()() ()、()(())、(())()、(() ()) 和((()))。

[输入格式]
输入一行包含一个字符串s,表示给定的括号序列,序列中只有左括号和右括号。
[输出格式]
输出一个整数表示答案,答案可能很大,请输出答案除以1000000007(即 1 0 9 10^9 109+7) 的余数。
[样例输入]

((()
[样例输出]
5

[评测用例规模与约定]

对于 40% 的评测用例,|s|≤200。对于所有评测用例,1≤|s|≤5000。

[题解]


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

/**
 * 2021 括号序列
 * 首先要明确一个合法的括号序列应满足条件:
 * ① '('和')'数量相同;
 * ② 对于该序列的任意前缀字符串,其中包含的 '(' 的数量均不少于 ')' 的数量
 * 括号序列问题是个典型的 dp 问题
 * 保证不能同时添加一对括号,使得该序列合法后不是最短的
 * 思考,我们能否将添加左右括号分开进行操作?
 * 由于新添的括号只能插在原先序列的空隙中,添加左右括号能否分开操作,只要看左右括号添加到一个间隔时,左右括号的顺序可能产生几种可能结果
 * 由于我们要保证添加后序列是最短的,添加的右括号一定要在左括号前面,即 添加左右括号操作可以分步进行
 * 对添加左括号进行 dp 分析
 * 我们记,状态 f(i,j) 表示 i 个括号中,左括号比右括号多 j 个的添加方案数
 * 可列,状态转移方程为:
 * s[i] = '(', f(i,j) = f(i - 1, j - 1)
 * s[i] = ')', f(i,j) = f(i - 1, j + 1) + f(i, j - 1)
 */
public class Main {

    static BufferedReader br;
    static StreamTokenizer st;
    static PrintWriter pw;

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


    private void go() throws Exception {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StreamTokenizer(br);
        pw = new PrintWriter(new OutputStreamWriter(System.out));

        getAns();

        pw.flush();
    }

    public static final int N = 5010;
    public static final int MOD = (int) 1e9 + 7;
    long[][] f = new long[N][N];
    char[] str = new char[N];
    int n;

    private void getAns() throws Exception {
        String s = br.readLine();
        n = s.length();
        for (int i = 1; i <= n; i++) {
            str[i] = s.charAt(i - 1);
        }
        // 计算应该添加的左括号方案数
        long l = calc();
        // 反转括号序列,对调左右括号,再调用相同方法,即可得到添加右括号方案数
        for (int i = 1, j = n; i <= j; i++, j--) {
            char tmp = str[i];
            str[i] = str[j];
            str[j] = tmp;
        }
        for (int i = 1; i <= n; i++) {
            if (str[i] == '(') str[i] = ')';
            else str[i] = '(';
        }
        long r = calc();
        pw.print(l * r % MOD);
    }

    /**
     * 计算添加左括号的方案数
     * @return
     */
    private long calc() {
        for (long[] longs : f) {
            Arrays.fill(longs, 0);
        }
        f[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            if (str[i] == '(') {
                for (int j = 1; j <= n; j++) {
                    f[i][j] = f[i - 1][j - 1];
                }
            } else {
                f[i][0] = (f[i - 1][1] + f[i - 1][0]) % MOD;
                for (int j = 1; j <= n; j++) {
                    f[i][j] = (f[i - 1][j + 1] + f[i][j - 1]) % MOD;
                }
            }
        }
        for(int i = 0; i <= n; i++) {
            // 这个结果有可能还会出现负数
            if (f[n][i] != 0) return f[n][i];
        }
        return -1;
    }
}

以上综合各位大佬提供给的思路和我自己的思路,自己写出的题解。

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值