2024年5月CSP校内选拔赛

2024年5月CSP校内选拔赛,回顾+题解。

得分245/400,AC了两个小难题,差强人意。

题解

A. 装饰品

考点:乘法原理,快速幂,费马小定理

题意:将 m 种元素填入 n 个盒子,要求不存在任何长度大于等于 2 的回文子串,求解填入的方法数。
(结果对 1e9 + 7 取模)(对所有数据,$1\leq n,m\leq1e18$)

分析:不难发现,只要满足不存在长度为 2,3 的回文子串,就不会出现回文子串。所以填入时只需保证填入的元素不与前两个位置相同即可。即 $m\times (m-1)\times(m-2)^{n-2}$ 对 1e9 + 7 取模为所求。

实际上,n 与 m 范围过大,需要考虑多个优化:
1)快速幂。
2)取模运算对加法友好,在开始前将 m 对 1e9 + 7 取模。
3)根据费马小定理,由 1e9 + 7 is prime,在开始前将 n 对 1e9 + 6 取模。

费马小定理:
$\ \ $若 p 是一个质数,且 a 不是 p 的倍数,则有 $a^{p-1}\equiv 1\ (mod\ p)$

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long

const int p = 1e9 + 7;

inline int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}

int qmi(int a, int k){
int res = 1;
while(k){
if(k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}

signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t = read();
while(t--){
int n = read() % (p - 1), m = read() % p;
if(n == 1){
cout << m % p << '\n';
}
else{
int ans = ((m % p) * ((m - 1) % p)) % p;
ans = (qmi(m - 2, n - 2) * ans) % p;
cout << ans << '\n';
}
}
return 0;
}

B. 幻兽帕鲁

题意:给定长度为 n 的数列,在其中取数,规定不能连续取超过 k 个数,求取出数的和的最大值。

分析:
1)朴素dp,从动态规划开始入手,规定 dp[i][j] 表示第 i 天,连续取出 j 个数时,已取出数的最大值。
状态转移:dp[i][0] 由前一天的 dp 最大值决定。
$j > 0$ 时,dp[i][j] = dp[i - 1][j - 1] + a[i]
(时间复杂度 $O(n^2)$​ 得分 50/100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<bits/stdc++.h>
using namespace std;

const int N = 1005;
int a[N], dp[N][N];

inline int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}

int main(){
int n = read(), k = read();
for(int i = 1; i <= n; i++)
a[i] = read();

for(int i = 1; i <= n; i++){
for(int j = 0; j <= k; j++){
dp[i][0] = max(dp[i][0], dp[i - 1][j]);
}
for(int j = 1; j <= k; j++){
dp[i][j] = dp[i - 1][j - 1] + a[i];
}
}

int ans = 0;
for(int i = 0; i <= k; i++){
ans = max(ans, dp[n][i]);
}
cout << ans;
return 0;
}

2)单调栈优化dp,不难发现,当dp[i][j]中,有 j1 < j2dp[i][j1] > dp[i][j2] 时,dp[i][j_2]是不必考虑的。使用单调栈维护每一个dp[i]的情况即可。AC代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<bits/stdc++.h>
using namespace std;
typedef pair<long long, int> PII;
#define int long long

const int N = 1e5 + 10;
int a[N];
vector<PII>dp[N];

inline int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}

signed main() {
int n = read(), k = read();
for (int i = 1; i <= n; i++)
a[i] = read();

dp[1].emplace_back(0, 0);
if (a[1] > 0) dp[1].emplace_back(a[1], 1);
for (int i = 2; i <= n; i++) {
auto tmp = dp[i - 1].back();
dp[i].emplace_back(tmp.first, 0);
int pos = upper_bound(dp[i - 1].begin(), dp[i - 1].end(), make_pair(dp[i][0].first - a[i], 1000000)) - dp[i - 1].begin();
for (int j = pos; j < dp[i - 1].size(); j++) {
if (dp[i - 1][j].second != k)
dp[i].emplace_back(dp[i - 1][j].first + a[i], dp[i - 1][j].second + 1);
}
}

cout << dp[n].back().first;

return 0;
}

补充

AC后非常满足,直到看到出题人题解(单调队列优化dp):

好一个正难则反。

C. 下水道鼠鼠

题意:图中一些点上给定若干块蛋糕(称为聚落),称在距离 s 内,存在蛋糕数为 s 的结点的点为宜居点,输出宜居点的个数及所有宜居点。

分析:考虑聚落向外扩散,初始化所有除聚落的点的宜居值为 -1,聚落的宜居值=蛋糕数量,开始时将所有点压入优先队列,向外更新宜居值即可。(感觉比前面两题简单www)

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;

const int N = 2e5 + 10;
int n, m, k;
vector<int> g[N]; bool vis[N];
priority_queue<PII> q;

inline int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}

int main() {
n = read(), m = read(), k = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
g[x].emplace_back(y), g[y].emplace_back(x);
}

for (int i = 1; i <= k; i++) {
int p = read(), s = read();
q.emplace(s, p);
}

while (!q.empty()) {
int s = q.top().first, u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
if (s == 0) continue;

for (auto& v : g[u]) {
if (!vis[v]) {
q.emplace(s - 1, v);
}
}
}

int res = 0;
for (int i = 1; i <= n; i++)
if (vis[i]) res++;
cout << res << '\n';

for (int i = 1; i <= n; i++)
if (vis[i]) cout << i << ' ';

return 0;
}