2022 CSP-J2 复现赛

16 篇文章 0 订阅
13 篇文章 0 订阅

2022 CSP-J T1 乘方

展开

本题请以文件输入输出方式进行提交,输入输出文件名是 pow.in / pow.out

该比赛已结束,您无法在比赛模式下递交该题目。您可以点击“在题库中打开”以普通模式查看和递交本题。

题目描述

小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 aa 和 bb,求 a^bab 的值是多少。

a^bab 即 bb 个 aa 相乘的值,例如 2^323 即为 33 个 22 相乘,结果为 2 \times 2 \times 2 = 82×2×2=8。

“简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。

小文很快意识到,她的程序里的变量都是 int 类型的。在大多数机器上,int 类型能表示的最大数为 2^{31} - 1231−1,因此只要计算结果超过这个数,她的程序就会出现错误。

由于小文刚刚学会编程,她担心使用 int 计算会出现问题。因此她希望你在 a^bab 的值超过 {10}^9109 时,输出一个 -1 进行警示,否则就输出正确的 a^bab 的值。

然而小文还是不知道怎么实现这份程序,因此她想请你帮忙。

输入格式

输入共一行,两个正整数 a, ba,b。

输出格式

输出共一行,如果 a^bab 的值不超过 {10}^9109,则输出 a^bab 的值,否则输出 -1

样例

输入数据#1

10 9

Copy

输出数据#1

1000000000

Copy

输入数据#2

23333 66666

Copy

输出数据#2

-1

Copy

提示

  • 对于 10 \%10% 的数据,保证 b = 1b=1。
  • 对于 30 \%30% 的数据,保证 b \le 2b≤2。
  • 对于 60 \%60% 的数据,保证 b \le 30b≤30,a^b \le {10}^{18}ab≤1018。
  • 对于 100 \%100% 的数据,保证 1 \le a, b \le {10}^91≤a,b≤109。

附件



点击下载

 

2022 CSP-J T2 解密

展开

本题请以文件输入输出方式进行提交,输入输出文件名是 decode.in / decode.out

该比赛已结束,您无法在比赛模式下递交该题目。您可以点击“在题库中打开”以普通模式查看和递交本题。

题目描述

给定一个正整数 kk,有 kk 次询问,每次给定三个正整数 n_i, e_i, d_ini​,ei​,di​,求两个正整数 p_i, q_ipi​,qi​,使 n_i = p_i \times q_ini​=pi​×qi​、e_i \times d_i = (p_i - 1)(q_i - 1) + 1ei​×di​=(pi​−1)(qi​−1)+1。

输入格式

第一行一个正整数 kk,表示有 kk 次询问。

接下来 kk 行,第 ii 行三个正整数 n_i, d_i, e_ini​,di​,ei​。

输出格式

输出 kk 行,每行两个正整数 p_i, q_ipi​,qi​ 表示答案。

为使输出统一,你应当保证 p_i \leq q_ipi​≤qi​。

如果无解,请输出 NO

样例

输入数据#1

10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109

Copy

输出数据#1

2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88

Copy

样例 #2~4

附件中的 decode/decode2~4.in 与 decode/decode2~4.ans

数据范围

以下记 m = n - e \times d + 2m=n−e×d+2。

保证对于 100\%100% 的数据,1 \leq k \leq {10}^51≤k≤105,对于任意的 1 \leq i \leq k1≤i≤k,1 \leq n_i \leq {10}^{18}1≤ni​≤1018,1 \leq e_i \times d_i \leq {10}^{18}1≤ei​×di​≤1018
,1 \leq m \leq {10}^91≤m≤109。

测试点编号k \leqk≤n \leqn≤m \leqm≤特殊性质
1110^310310^310310^3103保证有解
2210^310310^310310^3103
3310^310310^91096\times 10^46×104保证有解
4410^310310^91096\times 10^46×104
5510^310310^910910^9109保证有解
6610^310310^910910^9109
7710^510510^{18}101810^9109保证若有解则 p=qp=q
8810^510510^{18}101810^9109保证有解
9910^510510^{18}101810^9109
101010^510510^{18}101810^9109

2022 CSP-J T3 逻辑表达式

展开

本题请以文件输入输出方式进行提交,输入输出文件名是 expr.in / expr.out

该比赛已结束,您无法在比赛模式下递交该题目。您可以点击“在题库中打开”以普通模式查看和递交本题。

题目描述

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。

在一个逻辑表达式中,元素的值只有两种可能:00(表示假)和 11(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为 &)和“或”(符号为 |)。其运算规则如下:

0 \mathbin{\&} 0 = 0 \mathbin{\&} 1 = 1 \mathbin{\&} 0 = 00&0=0&1=1&0=0,1 \mathbin{\&} 1 = 11&1=1;

0 \mathbin{|} 0 = 00∣0=0,0 \mathbin{|} 1 = 1 \mathbin{|} 0 = 1 \mathbin{|} 1 = 10∣1=1∣0=1∣1=1。

在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

比如,表达式 0|1&0 的运算顺序等同于 0|(1&0);表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1

此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种“短路”的策略:在形如 a&b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 0a=0,那么整个逻辑表达式的值就一定为 00,故无需再计算 b 部分的值;同理,在形如 a|b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 1a=1,那么整个逻辑表达式的值就一定为 11,无需再计算 b 部分的值。

现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的“短路”各出现了多少次。需要注意的是,如果某处“短路”包含在更外层被“短路”的部分内则不被统计,如表达式 1|(0&1) 中,尽管 0&1 是一处“短路”,但由于外层的 1|(0&1) 本身就是一处“短路”,无需再计算 0&1 部分的值,因此不应当把这里的 0&1 计入一处“短路”。

输入格式

输入共一行,一个非空字符串 ss 表示待计算的逻辑表达式。

输出格式

输出共两行,第一行输出一个字符 0 或 1,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&b 和 a|b 的“短路”各出现了多少次。

样例

输入数据#1

0&(1|0)|(1|1|1&0)

Copy

输出数据#1

1
1 2

Copy

解释 #1

该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

0&(1|0)|(1|1|1&0)
=(0&(1|0))|((1|1)|(1&0)) //用括号标明计算顺序
=0|((1|1)|(1&0))   //先计算最左侧的 &,是一次形如 a&b 的“短路”
=0|(1|(1&0))       //再计算中间的 |,是一次形如 a|b 的“短路”
=0|1               //再计算中间的 |,是一次形如 a|b 的“短路”
=1

Copy

输入数据#2

(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0

Copy

输出数据#2

0
2 3

Copy

样例 #3/4

附件中的 expr3/4.in 与 exprexpr3/4.ans

数据范围

设 \lvert s \rvert∣s∣ 为字符串 ss 的长度。

对于所有数据,1 \le \lvert s \rvert \le {10}^61≤∣s∣≤106。保证 ss 中仅含有字符 01&|() 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 ss 中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表达式)。

测试点编号\lvert s \rvert \le∣s∣≤特殊条件
1 \sim 21∼233
3 \sim 43∼455
552000200011
662000200022
772000200033
8 \sim 108∼1020002000
11 \sim 1211∼12{10}^610611
13 \sim 1413∼14{10}^610622
15 \sim 1715∼17{10}^610633
18 \sim 2018∼20{10}^6106

其中:

  • 特殊性质 11 为:保证 ss 中没有字符 &
  • 特殊性质 22 为:保证 ss 中没有字符 |
  • 特殊性质 33 为:保证 ss 中没有字符 ( 和 )

提示

以下给出一个“符合规范的逻辑表达式”的形式化定义:

  • 字符串 0 和 1 是符合规范的;
  • 如果字符串 s 是符合规范的,且 s 不是形如 (t) 的字符串(其中 t 是符合规范的),那么字符串 (s) 也是符合规范的;
  • 如果字符串 a 和 b 均是符合规范的,那么字符串 a&ba|b 均是符合规范的;
  • 所有符合规范的逻辑表达式均可由以上方法生成。

 

2022 CSP-J T4 上升点列

展开

本题请以文件输入输出方式进行提交,输入输出文件名是 point.in / point.out

该比赛已结束,您无法在比赛模式下递交该题目。您可以点击“在题库中打开”以普通模式查看和递交本题。

题目描述

在一个二维平面内,给定 nn 个整数点 (x_i, y_i)(xi​,yi​),此外你还可以自由添加 kk 个整数点。

你在自由添加 kk 个点后,还需要从 n + kn+k 个点中选出若干个整数点并组成一个序列,使得序列中任意相邻两点间的欧几里得距离恰好为 11 而且横坐标、纵坐标值均单调不减,即 x_{i+1} - x_i = 1, y_{i+1} = y_ixi+1​−xi​=1,yi+1​=yi​ 或 y_{i+1} - y_i = 1, x_{i+1} = x_iyi+1​−yi​=1,xi+1​=xi​。请给出满足条件的序列的最大长度。

输入格式

第一行两个正整数 n, kn,k 分别表示给定的整点个数、可自由添加的整点个数。

接下来 nn 行,第 ii 行两个正整数 x_i, y_ixi​,yi​ 表示给定的第 ii 个点的横纵坐标。

输出格式

输出一个整数表示满足要求的序列的最大长度。

样例

输入数据#1

8 2
3 1
3 2
3 3
3 6
1 2
2 2
5 5
5 3

Copy

输出数据#1

8

Copy

输入数据#2

4 100
10 10
15 25
20 20
30 30

Copy

输出数据#2

103

Copy

样例 #3/4

附件中的 point3/4.in 与 point3/4.ans

第三个样例满足 k = 0k=0。

数据范围

保证对于所有数据满足:1 \leq n \leq 5001≤n≤500,0 \leq k \leq 1000≤k≤100。对于所有给定的整点,其横纵坐标 1 \leq x_i, y_i \leq {10}^91≤xi​,yi​≤109,且保证所有给定的点互不重合。对于自由添加的整点,其横纵坐标不受限制。

测试点编号n \leqn≤k \leqk≤x_i,y_i \leqxi​,yi​≤
1 \sim 21∼21010001010
3 \sim 43∼41010100100100100
5 \sim 75∼750050000100100
8 \sim 108∼1050050000{10}^9109
11 \sim 1511∼15500500100100100100
16 \sim 2016∼20500500100100{10}^9109

 

赛后题解

视频讲解:https://www.topscoding.com/video/635e2f482e0ba79e2e3fc58a#1667116872

T1 pow

极水的题,暴力循环求 a^bab 即可,中间如果出现超过 10^9109 的情况,就直接输出 -1−1。

注意两点:

  1. 数据类型?
  2. 会不会超时?
#include <bits/stdc++.h>
using namespace std;

long long pw(int a, int b) {
    if(b == 1) return a;
    if(a == 1) return 1;
    long long res = 1;
    for(int i = 1; i <= b; i++) {
        res *= a;
        if(res > 1e9) return -1;
    }
    return res;
}

int main(){
    freopen("pow.in", "r", stdin);
    freopen("pow.out", "w", stdout);
    int a, b;
    cin >> a >> b;
    cout << pw(a, b);
    return 0;
}

Copy

T2 decode

看题面里有两个关系式就知道,这题式一道数学题。本题有两种解法,两种解法都要求有一定初中数学功底。

数学:转化 (等量关系的转化)

方法一、纯数学

对第二个等式进行转化:
e\times d = (p-1)(q-1) + 1\\= p\times q - p - q + 1 + 1\\\Leftrightarrow p + q = p \times q - e \times d + 2e×d=(p−1)(q−1)+1=p×q−p−q+1+1⇔p+q=p×q−e×d+2
又因为第一个式子为 n = p\times qn=p×q,所以有:
p + q = n - e \times d + 2\\p+q = n-e\times d + 2\\p+q = mp+q=n−e×d+2p+q=n−e×d+2p+q=m
又因为 n = p*qn=p∗q,所以有:
\left\{\begin{matrix} p+q = m\\p*q = n\end{matrix}\right.{p+q=mp∗q=n​
把一式转为为 q = m - pq=m−p,然后带入二式,得:
p*(m-p)=n\\\Leftrightarrow p^2 - m\times p + n = 0p∗(m−p)=n⇔p2−m×p+n=0
如此,就转化成了一个一元二次方程的求解问题。

对于一般的一元二次方程 ax^2+bx+c=0ax2+bx+c=0 ,其求根公式为:
x_{1,2} = \frac{-b \pm \sqrt {b^2-4ac}}{2a}x1,2​=2a−b±b2−4ac​​
这里 a = 1, b = -m, c= na=1,b=−m,c=n

如果 b^2-4ac<0b2−4ac<0,也即 m^2 - 4n < 0m2−4n<0 (这叫做一元二次方程的判别式)则方程无解,输出 NO 即可。

参考代码:

#include <bits/stdc++.h>
using namespace std;
long long n, k, e, d, m, p, q;
void solve() {
    m = n - e*d + 2;
    if(m*m - 4*n < 0) {
        cout << "NO\n";
        return;
    } else {
        long long d = round(sqrt(m*m-4*n));
        if(d*d != m*m - 4*n) {  // 如果开根号不是整数,则无解
            cout << "NO\n";
            return;
        }
        p = (m-d)/2;
        q = m-p;
        cout << p << ' ' << q << '\n';
    }
}

int main(){
    freopen("decode.in", "r", stdin);
    freopen("decode.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> k;
    while(k--){
        cin >> n >> d >> e;
        solve();
    }
    return 0;
}

Copy


二分的思路:
\left\{\begin{matrix} p+q = m\\p*q = n\\p \le q\end{matrix}\right.⎩⎪⎨⎪⎧​p+q=mp∗q=np≤q​
p \le m/2p≤m/2

分析,易得:p*qp∗q 在 p\in [1, m/2]p∈[1,m/2] 上,单调递增,因此可以对 pp 进行二分查找。

参考代码:

#include <bits/stdc++.h>
using namespace std;

long long n, k, e, d, m, p, q;
int main(){
    // freopen("decode.in", "r", stdin);
    // freopen("decode.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> k;
    while(k--){
        cin >> n >> d >> e;
        long long m = n - e*d +2;
        long long l = 1, r = m/2, p, q;  // p <= q  所以 p <= m/2
        while(l <= r) {
            p = (l + r) / 2;
            q = m - p;
            if(n == p*q) {  // 在 p 的值域范围内,p*q 是单调递增的
                break;
            } else if(n < p*q) {
                r = p - 1;
            } else {
                l = p + 1;
            }
        }
        if(n == p*q) {
            cout << p << ' ' << q << '\n';
        } else {
            cout << "NO\n";
        }
    }
    return 0;
}

Copy

T3 expr

这题应该是四题里最难的一道了。对于计算机来说,必须构建出表达式树,才可以进行计算。而输入是中缀表达式,因此,需要先把中缀表达式转后缀表达式,然后基于后缀表达式构建出表达式树,最后进行计算。

1. 中缀转后缀

需要借助栈来实现从中缀表达式到后缀表达式的转换。

这里明确一下使用栈转换的算法思想:

从左到右开始扫描中缀表达式,遇到数字, 直接输出

遇到运算符时:

a. 若为“(” 直接入栈

b. 若为“)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出

c. 若为其他符号, 将符号栈中的元素依次出栈并输出, 直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。

扫描完后, 将栈中剩余符号依次输出。

2. 构建表达式树

逐次读取后缀表达式的每一个符号

如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中;

如果符号是操作数,则从栈中弹出两棵树 T1 和 T2(先弹出 T1),并形成一颗以操作符为根的树,其中 T1 为右儿子,T2 为左儿子;

然后将新的树压入栈中,继续上述过程。

3. 计算表达式值

DFS 遍历即可。表达式树中,叶子一定是数值 00 或 11,非叶子一定是 & 或者 |。DFS 过程:

  1. 遍历到叶子直接返回叶子的值。
  2. 遍历到非叶子时,先递归遍历左子树返回对应的子树的值。然后基于左子树的返回值和当前结点的运算符判断是否会短路:
    1. 1| 会发生“或短路”,并且返回 11
    2. 0& 会发生“与短路”,并且返回 00
    3. 非上面两种情况,计算右子树的值并返回其结果即可。

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
string s;
struct Node {
    int v, l, r;
} tr[N];
int num, ans1, ans2;
stack<char> ops;
stack<int> sta;
vector<char> sf;   // suffix 后缀表达式
/* 中缀转后缀:
从左到右开始扫描中缀表达式
遇到数字, 直接输出
遇到运算符
a.若为“(” 直接入栈
b.若为“)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出
c.若为其他符号, 将符号栈中的元素依次出栈并输出, 直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。
扫描完后, 将栈中剩余符号依次输出
*/
void in2sf() {
    for(int i = 0; i < s.size(); i++) {
        if(s[i] == '0' || s[i] == '1')  // 数字直接写下
            sf.push_back(s[i]);
        else if(s[i] == '(')  // 是 (
            ops.push(s[i]);
        else if(s[i] == ')') {  // 是 )
            while(!ops.empty() && ops.top() != '(') {
                sf.push_back(ops.top());  // 一直输出,直到碰到左括号
                ops.pop();
            }
            ops.pop();  // 弹出额外的 '('
        } else if(s[i] == '&') {   // 是 &
            while(!ops.empty() && ops.top() == '&') {
                sf.push_back(ops.top());
                ops.pop();
            }
            ops.push('&');
        } else {   // 是 |
            while(!ops.empty() && ops.top() != '(') {
                sf.push_back(ops.top());
                ops.pop();
            }
            ops.push('|');
        }
    }
    while(!ops.empty()) {
        sf.push_back(ops.top());
        ops.pop();
    }
}

void build() {
    for(int i = 0; i < sf.size(); i++) {
        if(sf[i] == '0' || sf[i] == '1') {
            tr[++num] = {sf[i]-'0', -1, -1};
            sta.push(num);
        } else {
            int r = sta.top(); sta.pop();
            int l = sta.top(); sta.pop();
            int v = (sf[i]=='&'?2:3);
            tr[++num] = {v, l, r};
            sta.push(num);
        }
    }
}

int dfs(int u) {
    if(tr[u].v == 0 || tr[u].v == 1) return tr[u].v;  // 是叶子(数字)结点
    int l = dfs(tr[u].l);
    if(l == 0 && tr[u].v == 2) {  // 0&
        ans1++;
        return 0;
    }
    if(l == 1 && tr[u].v == 3) {  // 1|
        ans2++;
        return 1;
    }
    int r = dfs(tr[u].r); 
    return r;  // 只要不短路,结果肯定就取决于右值  1&  0|
}

int main(){
    freopen("expr.in", "r", stdin);
    freopen("expr.out", "w", stdout);
    cin >> s;
    in2sf();   // 在构建表达式树前,需要把中缀表达式转后缀
    build();   // 利用后缀表达式构建表达式树
    cout << dfs(num) << '\n';  // 后缀表达式下,根在末尾,从根 dfs
    cout << ans1 << ' ' << ans2;
    return 0;
}

Copy

T4 point

题目大意:从 nn 个坐标中选择若干个,使得横坐标不减,且纵坐标不减,同时可以插入 kk 个任意位置的点,使得选择的点和差入的点相邻两个点之间距离为 11,求最大点的数量。

考虑一个更简单的问题:令 k=0k=0,则问题变成了从 nn 个坐标中选择若干个,使得横坐标不减,且纵坐标不减,相邻点之间距离为 11,求最大点数。

此时容易想到对 nn 个点排序,先按横坐标排序,再按纵坐标排序。如此,再对这 nn 个点求最长上升子序列即可。记 f[i]f[i] 为以 ii 结尾的最长上升点序列最大长度,则有:
f[i] = \max_{j\in[1,i-1]}(f[i], f[j] + 1)f[i]=j∈[1,i−1]max​(f[i],f[j]+1)
此时,我们再考虑 k>0k>0,只需对上述状态增加一维,记 f[i][p]f[i][p] 为以 ii 结尾,且已经插入了 pp 个额外点的最长上升点序列最大长度,则有:
f[i][p] = \max_{j\in[1,i-1], p\in[d,k]}(f[i][p], f[j][p-d] + d+1)\\d = a[i].x-a[j].x+a[i].y-a[j].y-1f[i][p]=j∈[1,i−1],p∈[d,k]max​(f[i][p],f[j][p−d]+d+1)d=a[i].x−a[j].x+a[i].y−a[j].y−1

#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
int n, k, f[505][105]; // f[i][j]: 前 i 个点,插入了 j 个点后最大长度
pair<int, int> a[505];

int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y;
    }
    sort(a+1, a+n+1);
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= k; j++) {
            f[i][j] = 1 + j; // 直接在 i 点前插入 j 个点
        }
    }
    // 类似最长上升子序列
    for(int i = 2; i <= n; i++) {
        for(int j = i-1; j >= 1; j--) {  // j -> i
            if(a[j].y > a[i].y) continue;
            // 从 j 到 i 要插入 d 个点才能满足
            int d = a[i].x-a[j].x + a[i].y - a[j].y - 1;
            for(int p = d; p <= k; p++) {
                f[i][p] = max(f[i][p], f[j][p-d] + d + 1);
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
        ans = max(ans, f[i][k]);
    cout << ans;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值