CSP-J2022第二轮题解
T1.乘方 ⊗ \otimes ⊗
简化题意:给定
a
,
b
a,b
a,b,求
a
b
a^b
ab 的值。若
a
b
>
1
0
9
a^b>10^9
ab>109,则输出 -1
。
- a , b ⩽ 1 0 9 a,b\leqslant10^9 a,b⩽109
看似需要快速幂,其实不然。当
a
>
1
a\gt 1
a>1 时,其实暴力乘的话乘不了几次的。也就是说,特判一下,如果
a
=
1
a=1
a=1,则输出 1
,否则直接暴力乘起来。
T2.解密 ⊗ \otimes ⊗
题意:给定一个正整数 k k k,有 k k k 次询问,每次给定三个正整数 n i , e i , d i n_i, e_i, d_i ni,ei,di,求两个正整数 p i , q i p_i, q_i pi,qi,使 n i = p i × q i n_i = p_i \times q_i ni=pi×qi、 e i × d i = ( p i − 1 ) ( q i − 1 ) + 2 e_i \times d_i = (p_i - 1)(q_i - 1) + 2 ei×di=(pi−1)(qi−1)+2。
非常明显的推式子。 e i × d i = p i ⋅ q i − p i − q i + 2 e_i\times d_i=p_i\cdot q_i-p_i-q_i+2 ei×di=pi⋅qi−pi−qi+2, ∵ n i = p i ⋅ q i \because n_i=p_i\cdot q_i ∵ni=pi⋅qi, ∴ e i × d i = n − p i − q i + 1 \therefore e_i\times d_i=n-p_i-q_i+1 ∴ei×di=n−pi−qi+1,即 p i + q i = n + 2 − e i × d i p_i+q_i=n+2-e_i\times d_i pi+qi=n+2−ei×di。
现有 { p i + q i = n + 2 − e i × d i p i × q i = n \begin{cases}p_i+q_i=n+2-e_i\times d_i\\ p_i\times q_i=n \end{cases} {pi+qi=n+2−ei×dipi×qi=n,可以令 q i = n p i , m = n + 2 − e i × d i q_i=\frac{n}{p_i},m=n+2-e_i\times d_i qi=pin,m=n+2−ei×di,则 p i + n p i = m p_i+\frac{n}{p_i}=m pi+pin=m,经整理得 p i 2 − m ⋅ p i + n = 0 {p_i}^2-m\cdot p_i+n=0 pi2−m⋅pi+n=0,用一元二次方程的方法求就好了。 Δ = m 2 − 4 n \Delta=m^2-4n Δ=m2−4n,则 p i = − b ± Δ 2 a p_i=\frac{-b\pm\sqrt\Delta}{2a} pi=2a−b±Δ,最后再验证一下,同样较为简单。
T3.逻辑表达式 ⊗ \otimes ⊗
又是逻辑运算……~~不知道为什么同样的东西 CCF 能在16道题中出现两次。~~这题还好,不是特别的难。主要是先看一下样例,思考一下自己是怎么运算的。这一步就先略过,主要讲最终的思路。我们倒着想,考虑一下结果是怎么产生的。显然,结果是一个类似于 x op y
的式子产生的。这时候可以分类讨论:如果是 1 | x
,那就产生了一次 | 短路
;如果是 0 & x
,那就产生了一次 & 短路
;否则这一次操作就没有短路。现在,我们就要延伸下去了。就拿 x op y
来看,在计算 x
与 y
的过程中,肯定也会产生一些短路,所以,接下来就要考虑 x
和 y
是怎么样得来的。这就是一个递归的过程。接下来,就要干的一件事情就是:通过给定的表达式 S
,找到这个表达式做的最后一个运算,进而将表达式拆分为两部分进行运算。如何找到表达式的最后一个运算呢?就要从题目中说的优先级讲起。首先,括号里的表达式可以直接忽略。其次,找到最后一个或运算。【原因:与运算比或运算优先】如果没有或运算,就选择最后一个与运算。到这里,我们就可以写出一份伪代码:
countAnd = 0, countOr = 0
func solve(l, r):
if str countain '|':
leftPart = solve(l, pos - 1)
if leftPart = 1: //统计短路次数
countOr++, return 1
return solve(pos + 1, r)
if str countain '&': //此时表达式中没有'|'运算了
leftPart = solve(1, pos - 1)
if leftPart = 0: //统计短路次数
countAnd++, return 0
return solve(pos + 1, r)
if str is likes "(xxx)":
return solve(l + 1, r - 1)
return 1 or 0
到这里,代码基本就写好了。如果仔细观察一下伪代码,会发现它的复杂度依然是 O ( n 2 log n ) O(n^2\log n) O(n2logn),因为寻找最后一个与/或运算还需要扫一遍。非常显然这种东西需要预处理,预处理的方法也很简单,在没有括号的情况下直接就能记录,有了括号之后用栈简单的维护一下就好了。
T4.上升点列 ⊗ \otimes ⊗
首先,假如说把 K K K 个备用点单独排成一行,那么答案就是 K K K。怎么做到优化呢?就是依靠题目中给定的点,有了这些点之后,就会节省一些备用点。如果说这 K K K 个备用点可以将 r e s res res 个点串在一起,那么答案就是 K + r e s K+res K+res,这道题的核心就是求出这个 r e s res res。
今年的题目好像还差一个动态规划没有出,就考虑动态规划吧。动态规划题从问题出发。问题:在 n n n 个点中,加入 k k k 个点所能达到的最长长度。那我们就可以得出状态: f i , j f_{i,j} fi,j 表示前 i i i 个点中加入 j j j 个点所能达到的最大长度。但是这个状态明显和点的顺序有关。我们要规定一种点的顺序。先来考虑转移方程在确定点的顺序吧。考虑 f i , j f_{i,j} fi,j 和 f i − 1 , j f_{i-1,j} fi−1,j 之间的差异:第 i i i 个点。第 i i i 个点可以与 1 ∼ i − 1 1\sim i-1 1∼i−1 中的点相连,暂且假设它与 p p p 点相连,可以得到点 i i i 与点 p p p 相连所需要的备用点数 d = x i − x p + y i − y p d=x_i-x_p+y_i-y_p d=xi−xp+yi−yp。那么可以得出: f i , j = max k = 1 i − 1 ( f k , d i s i , k + d i s i , k + 1 ) ⋅ [ d i s i , k ⩽ j ] f_{i,j}=\max_{k=1}^{i-1}(f_{k,dis_{i,k}}+dis_{i,k}+1)\cdot[dis_{i,k}\leqslant j] fi,j=maxk=1i−1(fk,disi,k+disi,k+1)⋅[disi,k⩽j],具体见代码。
代码
T1.乘方
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
for(; ch >= '0' && ch <= '9'; s = 10 * s + ch - '0', ch = getchar());
return s * w;
}
signed main(){
int a = read(), b = read();
if(a == 1){
cout << 1 << endl;
return 0;
}
int res = 1;
for(int i = 1; i <= b; i++){
res *= a;
if(res > 1e9) {
cout << "-1" << endl;
return 0;
}
}
cout << res << endl;
return 0;
}
T2.解密
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; w *= (ch == '-' ? -1 : 1), ch = getchar());
for(; ch >= '0' && ch <= '9'; s = s * 10 + ch - '0', ch = getchar());
return s * w;
}
//545 1 499
signed main(){
// freopen("decode.in", "r", stdin);
// freopen("decode.out", "w", stdout);
int T = read();
while(T--){
int n = read(), b1 = read(), b2 = read();
int m = n + 2 - b1 * b2;
int delta = m * m - 4 * n;
if(delta < 0){
cout << "NO" << endl;
continue;
}
int sqrt_delta = sqrt(delta) + 0.05;
int p1 = (m + sqrt_delta) >> 1;
int p2 = (m - sqrt_delta) >> 1;
int q1 = n / p1;
int q2 = n / p2;
if(p1 + q1 == m && p1 * q1 == n){
cout << min(p1, q1) << " " << max(p1, q1) << endl;
// printf("%lld+%lld=%lld,%lld*%lld=%lld", p1, q1, m, p1, q1, n);
} else if (p2 + q2 == m && p2 * q2 == n){
cout << min(p2, q2) << " " << max(p2, q2) << endl;
// printf("%lld+%lld=%lld,%lld*%lld=%lld", p2, q2, m, p2, q2, n);
} else {
cout << "NO" << endl;
}
}
return 0;
}
T3.逻辑表达式
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
for(; ch >= '0' && ch <= '9'; s = 10 * s + ch - '0', ch = getchar());
return s * w;
}
const int MAXN = 1000005;
int cnt_and, cnt_or, lastand[MAXN], lastor[MAXN], resand[MAXN], resor[MAXN];
string s;
int solve(int l, int r){
if(resor[r] >= l){
int res = solve(l, resor[r] - 1);
if(res == 1) {
cnt_or++;
return 1;
}
return solve(resor[r] + 1, r);
}
if(resand[r] >= l){
int res = solve(l, resand[r] - 1);
if(res == 0){
cnt_and++;
return 0;
}
return solve(resand[r] + 1, r);
}
if(s[l] == '(' && s[r] == ')') return solve(l + 1, r - 1);
return (s[l] - '0');
}
signed main(){
cin >> s;
int n = s.size();
s = " " + s;
int top = 0;
for(int i = 1; i <= n; i++){
if(s[i] == '&') lastand[top] = i;
else if (s[i] == '|') lastor[top] = i;
else if (s[i] == '(') top++;
else if (s[i] == ')') lastand[top] = lastor[top] = 0, top--;
resand[i] = lastand[top], resor[i] = lastor[top];
}
int res = solve(1, n);
cout << res << endl;
cout << cnt_and << " " << cnt_or << endl;
return 0;
}
T4.上升点列
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; w *= (ch == '-' ? -1 : 1), ch = getchar());
for(; ch >= '0' && ch <= '9'; s = s * 10 + ch - '0', ch = getchar());
return s * w;
}
const int MAXN = 505;
const int MAXK = 105;
struct Node{
int x, y;
bool operator < (const Node &rhs) const {
if(x == rhs.x) return y < rhs.y;
return x < rhs.x;
}
} a[MAXN];
int f[MAXN][MAXK];
signed main(){
int n = read(), K = read();
for(int i = 1; i <= n; i++){
a[i].x = read(), a[i].y = read();
}
sort(a + 1, a + 1 + n);
// for(int i = 1; i <= n; i++){
// printf("(%lld,%lld)\n", a[i].x, a[i].y);
// }
for(int i = 1; i <= n; i++){
f[i][0] = 1;
}
for(int i = 1; i <= n; i++){
for(int j = 1, cnt; j < i; j++){
if(a[i].y < a[j].y) continue;
cnt = a[i].x - a[j].x + a[i].y - a[j].y - 1;
if(cnt > K){
// cout << "Too long" << endl;
continue;
}
// printf("%lld(%lld,%lld) %lld(%lld,%lld) %lld\n", j, a[j].x, a[j].y, i, a[i].x, a[i].y, cnt);
for(int k = cnt; k <= K; k++){
f[i][k] = max(f[i][k], f[j][k - cnt] + cnt + 1);
}
}
}
int ans = -1;
for(int i = 1; i <= n; i++){
for(int j = 0; j <= K; j++){
ans = max(ans, f[i][j] + K - j);
}
}
cout << ans << endl;
return 0;
}
结语
考前做了很多预测,都实现了。比如第三题比第四题难、会考到与或非逻辑运算之类的。这一次我的成绩也还不错,整体挺顺利的。