10.15模拟赛总结
总结
估分:\(30 + [0, 5] + [30, 100] + 60 = [120, 195]\)
得分:\(30 + 0 + 100 + 60 = 190\)
\(\texttt{Rk.6}\)
情景剧 \(\texttt{tv}\)
题意
在 \(n\) 个数中选一些数 \(l \sim r\),求 \(\forall 1 \le l \le r \le n\),\(\max\{h_l, \ldots, h_r\} \times \min\{h_l, \ldots, h_r\} \times (r - l + 1)\) 的最大值。
分析
枚举哪个值是最小值,因为随着区间变化,最大值是不降的,满足单调性,然后需要求最大区间,一共只有 \(n\) 个区间。
最小值的区间可以用单调栈维护一下。最大值用 ST 表维护一下子就可以了。
时间复杂度 \(\Theta(n \log_2 n)\),能过。
重要的事情说三遍:
开 __int128!!!
开 __int128!!!
开 __int128!!!
upd:题解说可以用笛卡尔树做,我不会就是了qwq。
代码
点击查看代码
#include <bits/stdc++.h>
#define ll __int128
using namespace std;
const int kMaxN = 2e6 + 5;
int n;
int top, maxn, p, t;
int sl[kMaxN], sr[kMaxN], f[kMaxN][21], w[kMaxN];
ll ma;
ll read() {
ll 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 << 1) + (x << 3) + ch - '0';
ch = getchar();
}
return x * f;
}
void put(ll x) {
if (x < 0) {
putchar('-');
put(-x);
return;
}
if (!x) {
return;
}
put(x / 10);
putchar(x % 10 + '0');
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
n = read();
for (int i = 1; i <= n; i++) {
f[i][0] = read();
while (top && f[w[top]][0] >= f[i][0]) top--;
sl[i] = w[top];
w[++top] = i;
}
top = 0;
for (int i = n; i; i--) {
while (top && f[w[top]][0] >= f[i][0]) top--;
sr[i] = w[top];
if (sr[i] == 0) sr[i] = n + 1;
w[++top] = i;
}
for (int j = 1; j <= 21; j++) {
for (int i = 0; i <= n - (1 << j) + 1; i++) {
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
for (int i = 1; i <= n; i++) {
if (f[i][0] > 0) {
t = __lg(sr[i] - 1 - sl[i]);
maxn = max(f[sl[i] + 1][t], f[sr[i] - 1 - (1 << t) + 1][t]);
ma = max(((sr[i] - 1ll - sl[i] * (__int128)(1ll)) * (__int128)(1ll) * f[i][0]) * (maxn), ma);
}
}
put(ma);
return 0;
}
抽卡 \(\texttt{card}\)
题意
没写。。
分析
没写。。
代码
点击查看代码
没写。。
修改01序列 \(\texttt{mo}\)
题意
给定一个序列 \(a_1, \ldots, a_n\),保证 \(a_i \in \{\texttt{0}, \texttt{1}\}\),每次操作可以将一个 \(\texttt{0}\) 修改成 \(\texttt{1}\) 或者将 \(\texttt{1}\) 修改成 \(\texttt{0}\),修改完后需要满足任意的相邻的两个 \(\texttt{1}\) 之间的距离都是 \(d\) 的倍数,给定 \(d\),求最小操作数。
分析
整场比赛最简单的题(确信
显然易见,那个将 \(\texttt{0}\) 修改成 \(\texttt{1}\) 的操作没有任何用,除非题目改成相邻的两个 \(\texttt{1}\) 间必须间隔 \(d\)。
然后,分析一下,我的想法:
Part 1
枚举以哪个点为起点,暴力跳,看能经过多少个 \(\texttt{1}\),然后没经过的 \(\texttt{1}\) 就是需要改的,时间复杂度 \(\Theta(\dfrac{n^2}{d})\)。
代码不放了,极其简单。
Part 2
因为从 \(d\) 之后的点跳根本没有意义,所以从哪个点开始只需要看 \(1 \sim d\) 就行了。
然后优化完之后时间复杂度似乎是 \(\Theta(n)\) 的。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, d;
int mp[100005];
int s1;
int ans = 0x7fffffff;
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> d;
for (int i = 1; i <= n; i++) {
cin >> mp[i];
if (mp[i]) {
s1++;
}
}
for (int i = 1; i <= d; i++) {
int sum = 0;
for (int j = i; j <= n; j += d) {
if (mp[j]) {
sum++;
}
}
ans = min(ans, s1 - sum);
}
cout << ans << '\n';
return 0;
}
Part 3
你说的对,但是若我卡掉了 \(\Theta(n)\),阁下又该如何应对呢?
不难看出,每个点只会被一个起点给走到,显然易见的,使第 \(i\) 被遍历到的起点就是 \(i \bmod d\),开个桶统计一下,就用 \(O(d)\) 的时间复杂度 AC 了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, d;
int mp[100005];
int s1;
int ans = 0x7fffffff;
int t[100005];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> d;
for (int i = 1; i <= n; i++) {
cin >> mp[i];
if (mp[i]) {
s1++;
t[i % d]++;
}
}
for (int i = 0; i < d; i++) {
ans = min(ans, s1 - t[i]);
}
cout << ans << '\n';
return 0;
}
Part 4
我们充分发扬人类智慧,枚举每个 \(\texttt{1}\) 为起点,然后看能经过多少 \(\texttt{1}\),然后就过了,但是似乎是可以被卡掉的。
然后某些神奇的东西优化一下就行了。
from guyushikunkun
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N], len, n, m, num, val, sum[N], k, res, id[N], ans;
int x[N], y[N];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i]) {
id[++len] = i;
}
}
ans = 1e9;
for (int j = 2; j <= min(len, 1000); j++) {
int last = id[j - 1], res = j - 2;
if (last > m) {
break;
}
for (int i = j; i <= len; i++) {
if ((id[i] - last) % m == 0) {
last = id[i];
} else {
res++;
}
}
ans = min(ans, res);
}
cout << ans << endl;
return 0;
}
集合 \(\texttt{set}\)
题意
给定一个正整数 \(n\),求集合 \(\{1, 2, \ldots, n\}\) 的所有非空子集和的乘积对 \(998244353\) 取模的结果。
分析
我们设 \(dp_{i, j}\) 表示选 \(i\) 个数,子集合是 \(j\) 的方案数,那么答案需要乘上 \(j^{dp_{i, j}}\),然后 \(i\) 可以压掉,式子随便推:\(dp_{j} = \sum dp_{j - i} (1 \le i \le n, i \le j \le n^2)\)。
然后根据费马小定理(\(a^{p - 1} \equiv 1 \pmod p\)),\(j^{dp_{j}} = j^{dp_{i, j} \bmod (p - 1)}\)。(\(p = 998244353\))
没了,简单。
重要的话说三遍:
记得开 __int128!!!
记得开 __int128!!!
记得开 __int128!!!
代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMaxN = 205, mod = 998244353;
int n, ans = 1, res;
__int128 dp[kMaxN * kMaxN];
int qpow(int a, __int128 n) {
for (res = 1; n; n >>= 1) (n & 1) && (res = res * a % mod), a = a * a % mod;
return res;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = n * n; j >= i; j--) {
dp[j] += dp[j - i];
dp[j] %= mod - 1;
}
}
for (int i = 1; i <= (n + 1) * (n) / 2; i++) {
ans *= qpow(i, dp[i] % (mod - 1));
ans %= mod;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号