模拟赛链接
排名:\(\text{rank 9}\)
分数:\(0+0+100+60=160\)
第一第二题我连暴力都没打出来我是什么废物。
T1:情景剧 / tv
题目描述:
给你一个长度为 \(n\) 的序列 \(a_1,a_2,\dots a_n\),请求出一个区间 \([l,r]\),使得这个区间里的最大值乘最小值乘这个区间的长度的值最大,输出这个最大值。(\(n\le 2\times 10^6,a_i\le 10^9\))
思路:
我们考虑先枚举最小值,肯定要让区间长度最大,所以我们可以分别找到这个最小值的左边和右边第一个比他小的数的右边或左边,也就是要让这一段的区间长度最大的前提下最小值还是这个数,这很明显可以用单调栈求解,然后最大值一定就定下来了,这可以用 st 表求解。注意结果可能过大需要高精度。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 2e6 + 5, kL = 22;
int n, a[kMaxN], lg[kMaxN], f[kMaxN][kL], pre[kMaxN], suf[kMaxN], stk[kMaxN], top;
string s;
__int128 ans;
int Get(int l, int r) {
int t = lg[r - l + 1];
return max(f[l][t], f[r - (1 << t) + 1][t]);
}
int main() {
freopen("tv.in", "r", stdin);
freopen("tv.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0);
cin >> n, lg[0] = -1;
for (int i = 1; i <= n; i++) {
cin >> a[i], f[i][0] = a[i], lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= n; i++) {
for (; top && a[stk[top]] > a[i]; top--) {
}
pre[i] = stk[top] + 1, stk[++top] = i;
}
top = 0;
for (int i = n; i; i--) {
for (; top && a[stk[top]] > a[i]; top--) {
}
suf[i] = (stk[top] ? stk[top] - 1 : n), stk[++top] = i;
}
for (int j = 1; j < kL; j++) {
for (int i = 1; i + (1 << j - 1) - 1 <= n; i++) {
f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
for (int i = 1; i <= n; i++) {
ans = max(ans, (__int128)1 * Get(pre[i], suf[i]) * a[i] * (suf[i] - pre[i] + 1));
}
for (; ans; s += ans % 10 + '0', ans /= 10) {
}
reverse(s.begin(), s.end()), cout << s;
return 0;
}
T2:抽卡 / card
无……
T3:修改 01 序列 / mo
题目描述:
给你一个长度为 \(n\) 的 01 序列 \(a\),你可以操作序列花费 \(1\) 的代价将序列里的其中一个数从 \(1\) 变成 \(0\) 或从 \(0\) 变成 \(1\)。(\(1\le d\le n\le 10^5\))
思路:
首先我们可以发现,将 \(0\) 变成 \(1\) 的操作是没有必要的,如果要满足要求那么至少要有一个 \(k\) 使得 \(p \equiv k \pmod d\)(\(p\) 为任意 \(a_p=1\) 的 \(p\))那么我们可以枚举这个 \(k\),然后用一个桶预处理出位置除以 \(d\) 余数为 \(r\) 的这个位置为 \(1\) 的位置的数量,然后再预处理出整个序列一共有多少个 \(1\),然后就没了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e5 + 5;
int n, d, cnt[kMaxN], ans = 1e9;
int main() {
freopen("mo.in", "r", stdin);
freopen("mo.out", "w", stdout);
cin >> n >> d;
for (int i = 1, a; i <= n; i++) {
cin >> a, cnt[i % d] += a, cnt[d] += a;
}
for (int i = 0; i < d; i++) {
ans = min(ans, cnt[d] - cnt[i]);
}
cout << ans;
return 0;
}
T4:集合 / set
题目描述
给定正整数 \(n\),计算 \(n\) 个元素的集合 \(\{1, 2, ..., n\}\),所有非空子集的乘积取模 \(998244353\) 后的结果。(\(n\le 200\))
思路:
看到子集,第一眼想到动态规划(暴搜),其实这就很像一个 01 背包,设 \(f_{i,j}\) 为选到第 \(i\) 个元素,当前的和为 \(j\) 的子集数量,那么答案就是 \(\sum i^{f_{n,i}} \mod 998244353\),很容易求出转移方程为:
但是 \(f_{n,i}\) 是指数,无法直接取模,我们可以用到费马小定理:
若 \(p\) 为质数,且 \(a,p\) 互质,那么就有 \(a^{p-1}\equiv 1 \pmod p\)
所以我们可以将所有 \(f_{i,j}\) 对 \(998244353-1\) 取模即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int kMaxN = 205, kM = 998244353;
int n;
LL f[kMaxN][kMaxN * kMaxN], ans = 1;
LL fpow(LL a, LL b, LL p, LL ret = 1) {
for (; b; b >>= 1, a = a * a % p) {
(b & 1) && (ret = ret * a % p);
}
return ret;
}
int main() {
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
cin >> n;
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n * (n + 1) / 2; j++) {
f[i][j] = f[i - 1][j];
j >= i && (f[i][j] = (f[i][j] + f[i - 1][j - i]) % (kM - 1));
}
}
for (int i = 1; i <= n * (n + 1) / 2; i++) {
ans = ans * fpow(i, f[n][i], kM) % kM;
}
cout << ans;
return 0;
}
标签:10,le,15,int,top,kMaxN,stk,2023,LL
From: https://www.cnblogs.com/lrx-blogs/p/2023-10-15-contest-summary.html