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;
}
posted @ 2023-10-15 17:12  Yun_Mengxi  阅读(27)  评论(0)    收藏  举报