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≤ | 特殊性质 |
---|---|---|---|---|
11 | 10^3103 | 10^3103 | 10^3103 | 保证有解 |
22 | 10^3103 | 10^3103 | 10^3103 | 无 |
33 | 10^3103 | 10^9109 | 6\times 10^46×104 | 保证有解 |
44 | 10^3103 | 10^9109 | 6\times 10^46×104 | 无 |
55 | 10^3103 | 10^9109 | 10^9109 | 保证有解 |
66 | 10^3103 | 10^9109 | 10^9109 | 无 |
77 | 10^5105 | 10^{18}1018 | 10^9109 | 保证若有解则 p=qp=q |
88 | 10^5105 | 10^{18}1018 | 10^9109 | 保证有解 |
99 | 10^5105 | 10^{18}1018 | 10^9109 | 无 |
1010 | 10^5105 | 10^{18}1018 | 10^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 中仅含有字符 0
、1
、&
、|
、(
、)
且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 ss 中没有重复的括号嵌套(即没有形如 ((a))
形式的子串,其中 a
是符合规范的逻辑表达式)。
测试点编号 | \lvert s \rvert \le∣s∣≤ | 特殊条件 |
---|---|---|
1 \sim 21∼2 | 33 | 无 |
3 \sim 43∼4 | 55 | 无 |
55 | 20002000 | 11 |
66 | 20002000 | 22 |
77 | 20002000 | 33 |
8 \sim 108∼10 | 20002000 | 无 |
11 \sim 1211∼12 | {10}^6106 | 11 |
13 \sim 1413∼14 | {10}^6106 | 22 |
15 \sim 1715∼17 | {10}^6106 | 33 |
18 \sim 2018∼20 | {10}^6106 | 无 |
其中:
- 特殊性质 11 为:保证 ss 中没有字符
&
。 - 特殊性质 22 为:保证 ss 中没有字符
|
。 - 特殊性质 33 为:保证 ss 中没有字符
(
和)
。
提示
以下给出一个“符合规范的逻辑表达式”的形式化定义:
- 字符串
0
和1
是符合规范的; - 如果字符串
s
是符合规范的,且s
不是形如(t)
的字符串(其中t
是符合规范的),那么字符串(s)
也是符合规范的; - 如果字符串
a
和b
均是符合规范的,那么字符串a&b
、a|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∼2 | 1010 | 00 | 1010 |
3 \sim 43∼4 | 1010 | 100100 | 100100 |
5 \sim 75∼7 | 500500 | 00 | 100100 |
8 \sim 108∼10 | 500500 | 00 | {10}^9109 |
11 \sim 1511∼15 | 500500 | 100100 | 100100 |
16 \sim 2016∼20 | 500500 | 100100 | {10}^9109 |
赛后题解
视频讲解:https://www.topscoding.com/video/635e2f482e0ba79e2e3fc58a#1667116872
T1 pow
极水的题,暴力循环求 a^bab 即可,中间如果出现超过 10^9109 的情况,就直接输出 -1−1。
注意两点:
- 数据类型?
- 会不会超时?
#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|
会发生“或短路”,并且返回 110&
会发生“与短路”,并且返回 00- 非上面两种情况,计算右子树的值并返回其结果即可。
参考代码:
#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;
}