比赛链接:
https://pintia.cn/problem-sets/1574060137151397888
A.Yet Another Remainder
题意:
给定一个大数 \(x\),长为 \(n\),已知 \(m(m = min(100, n))\) 行,第 \(i\) 行第 \(j\) 个数表示大数的第 \(j\) 位开始以步长为 \(i\) 的选法,选出的数的和,现在有 \(q\) 次询问,每个给定质数 \(p\),输出大数 mod \(p\) 是多少。
思路:
由费马小定理可得 \(x^{p - 1} \equiv 1(mod p)\),大数的每一位要乘上一个 \(10^i\),如果按照长为 \(p\) 的循环节进行分组,每一组的 \(10^i mod p\) 是相同的,且每一组的和就是 \(b[p - 1][i](i = 10^i mod p)\),就是题目给出的条件。\(10^i mod p\) 的数组可以预处理。
当 \(n <= p - 1\) 的情况就解决了。
当 \(n > p - 1\),因为 \(p <= 97\),那么大数的每一位已知了,即 \(b[n][j]\),直接暴力求即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 110;
int b[N][N], fac[N];
void solve(){
int n;
cin >> n;
for (int i = 1; i <= min(n, 100); i ++ )
for (int j = 1; j <= i; j ++ )
cin >> b[i][j];
int q;
cin >> q;
while(q -- ){
int p, ans = 0;
cin >> p;
if (p - 1 <= n){
fac[0] = 1;
for (int i = 1; i <= p - 1; i ++ )
fac[i] = fac[i - 1] * 10 % p;
for (int i = 1; i <= p - 1; i ++ )
ans = (ans + b[p - 1][i] * fac[(n - i) % (p - 1)]) % p;
}
else{
for (int i = 1; i <= n; i ++ )
ans = (ans * 10 + b[n][i]) % p;
}
cout << ans << "\n";
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
B.Non-decreasing Array
题意:
长为 \(n\) 的非递减序列 \(a\),当当前序列的长度为 \(m\) 时,每次操作可以先选择(也可以不选)一个元素 \(a_i(1 < i < m)\) 然后删除它,接着再选一个数 \(a_i(1 < i < m)\),将它变成任何一个数,但要保证序列还是非递减的,现在可以进行 \(k(1 <= k <= n)\) 次操作,问操作之后 \(\sum_{i = 2}^{m} (a_i - a_{i - 1})\) 的最大值是多少,对于每个 \(k\) 答案是独立的。
思路:
对于一个序列,最后剩下的肯定是两边的值,定义 \(dp[i][j]\) 表示保存第 \(i\) 位和第 1 位,然后删除了 \(j\) 个元素的最大值。
从小到大开始枚举删除了几个数,求答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 110;
LL a[N], dp[N][N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> a[i];
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= i - 2; j ++ )
for (int k = 0; k <= j; k ++ )
dp[i][j] = max(dp[i][j], dp[i - k - 1][j - k] + (a[i - k - 1] - a[i]) * (a[i - k - 1] - a[i]));
for (int k = 1; k <= n; k ++ )
cout << dp[n][min(2 * k, n - 2)] << "\n";
return 0;
}
E.An Interesting Sequence
题意:
构造一个长为 \(n\) 的序列 \(a\),\(a_1 = k(k > 1)\),且 \(a_i > 1\),\(gcd(a[i - 1], a[i]) = 1(1 < i <= n)\),问 \(\sum_{i = 1}{n} a_i\) 最小。
思路:
最小的两个大于 1 的互质的数就是 2 和 3。因为第一位是 \(k\),第二位先找到一个与 \(k\) 互质的数,设为 \(p\),如果它和 2 互质,后面就填 2 3 2 3...,否则填 3 2 3 2...。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, k;
cin >> n >> k;
auto isprime = [&](int x){
for (int i = 2; i * i <= x; i ++ )
if (x % i == 0)
return false;
return true;
};
int p = 0;
for (int i = 2; ; i ++ ){
if (gcd(i, k) == 1 && isprime(i)){
p = i;
break;
}
}
LL ans = k + p;
int x;
if (gcd(p, 2) == 1) x = 2;
else x = 3;
for (int i = 3; i <= n; i ++ ){
ans += x;
x = 5 - x;
}
cout << ans << "\n";
return 0;
}
F.Infinity Tree
题意:
刚开始树有一个节点,每秒钟每个节点(按照编号从小到大依次来)会多出 \(k\) 个子节点,编号由小到大依次增加,\(t\) 次询问,每次告诉 \(k\),求 \(x\) 和 \(y\) 的 \(lca\)。
思路:
开 int128 暴力求 \(lca\) 即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL k, x, y;
__int128 get(LL x){
__int128 ans = 1;
while(ans * (k + 1) < x)
ans *= (k + 1);
return ans;
}
void solve(){
cin >> k >> x >> y;
__int128 p = get(x), q = get(y);
while(x != y){
if (x > y){
x = (x - p + k - 1) / k;
p = get(x);
}
else{
y = (y - q + k - 1) / k;
q = get(y);
}
}
cout << x << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
J.A Game about Increasing Sequences
题意:
有一个数组,\(Alice\) 和 \(Bob\) 轮流取数,每次取开头或者结尾的数,要求除了第一个数外其他取的数要严格大于前一个人取的数,不能操作的人输,\(Alice\) 先手,如果先手胜输出 "Alice",否则输出 "Bob"。
思路:
不论后手怎么操作,先手跟就是了,所以只有当前面和后面的递增序列的长度都为偶数的时候,才输出 "Bob",否则 \(Alice\) 选择一条奇数的路径就能赢。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
vector <int> a(n);
for (int i = 0; i < n; i ++ )
cin >> a[i];
int L = 1, R = 1;
for (int i = 1; i < n; i ++ , L ++ )
if (a[i - 1] >= a[i])
break;
for (int i = n - 2; i >= 0; i -- , R ++ )
if (a[i] <= a[i + 1])
break;
if (L % 2 == 0 && R % 2 == 0) cout << "Bob\n";
else cout << "Alice\n";
return 0;
}
L.Quadruple
题意:
给定一个只包含 'I','C','P' 的字符串,进行 \(q\) 次询问,输出每次询问的区间中包含 "ICPC" 子序列的数量之和。
思路:
通过前缀和统计各种组合的答案,通过做差求答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353, N = 2e6 + 10;
using LL = long long;
struct data{
LL I, C, P;
LL IC, CP, PC;
LL ICP, CPC;
LL ICPC;
void Mod(){
I %= mod, C %= mod, P %= mod;
IC %= mod, CP %= mod, PC %= mod;
ICP %= mod, CPC %= mod;
ICPC %= mod;
}
}d[N];
pair <int, int> q[N];
LL countPC(int L, int R){
return (d[R].PC - d[L - 1].PC
- d[L - 1].P * (d[R].C - d[L - 1].C)) % mod;
}
LL countCPC(int L, int R){
return (d[R].CPC - d[L - 1].CPC
- d[L - 1].C * countPC(L, R) % mod
- d[L - 1].CP * (d[R].C - d[L - 1].C) % mod) % mod;
}
LL countICPC(int L, int R){
return (d[R].ICPC - d[L - 1].ICPC
- d[L - 1].I * countCPC(L, R) % mod
- d[L - 1].IC * countPC(L, R) % mod
- d[L - 1].ICP * (d[R].C - d[L - 1].C) % mod) % mod;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, Q;
cin >> n >> Q;
string s;
cin >> s;
s = '-' + s;
for (int i = 1; i <= n; i ++ ){
d[i] = d[i - 1];
if (s[i] == 'I'){
d[i].I ++ ;
}
else if (s[i] == 'C'){
d[i].C ++ ;
d[i].IC += d[i - 1].I;
d[i].PC += d[i - 1].P;
d[i].CPC += d[i - 1].CP;
d[i].ICPC += d[i - 1].ICP;
}
else if (s[i] == 'P'){
d[i].P ++ ;
d[i].CP += d[i - 1].C;
d[i].ICP += d[i - 1].IC;
}
d[i].Mod();
}
LL x, a, b, p;
cin >> x >> a >> b >> p;
for (int i = 0; i < Q; i ++ ){
x = (a * x + b) % p;
q[i].first = x % n + 1;
}
for (int i = 0; i < Q; i ++ ){
x = (a * x + b) % p;
q[i].second = x % n + 1;
}
LL ans = 0;
for (int i = 0; i < Q; i ++ ){
auto [L, R] = q[i];
if (L > R) swap(L, R);
ans = (ans + countICPC(L, R)) % mod;
}
cout << (ans + mod) % mod << "\n";
return 0;
}