2025-08-04 模拟赛总结 😀

预期:\(100+100+100+20=320\)
实际:\(100+100+100+20=320\)
排名:\(rk35/166\)

比赛链接:http://oj.daimayuan.top/contest/370

A - 宝石展览:

题意:

\(n\) 种宝石,每种宝石有 \(a_i\) 颗,价值为 \(v_i\)。一个宝石展览为将若干个宝石取出来,展览方案的华丽值计算规则为:对于每种颜色 \(i\),若方案中不包含该颜色的宝石,则华丽值不变;否则,若该颜色的宝石有 \(c_i\) 颗,则华丽值增加 \((v_i)^{c_i}\)。求所有宝石展览的华丽值之和。

思路:

考虑计算每个宝石的贡献,枚举宝石出现的次数,那么贡献为 \(\sum_{j=1}^{a_i}{a_i\choose j}(v_i)^j=(v_i+1)^{a_i}-1\)。全部累和就可以了。

代码:

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2e5 + 5, kM = 1e9 + 7;

int n, a[kMaxN], v[kMaxN], ans;
long long cnt;

long long fpow(long long a, long long b, long long p, long long r = 1) {
  for (a = (a % p + p) % p; b; b & 1 && (r = r * a % p), a = a * a % p, b >>= 1) {
  }
  return r % p;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i], cnt += a[i];
  }
  for (int i = 1; i <= n; i++) {
    cin >> v[i];
  }
  for (int i = 1, x, y; i <= n; i++) {
    x = (fpow(v[i] + 1, a[i], kM) - 1 + kM) % kM;
    y = fpow(2, cnt - a[i], kM);
    ans = (ans + 1LL * x * y) % kM;
  }
  cout << ans;
  return 0;
}

B - 乘积:

题意:

给定两个数 \(n,m\),求有多少长度为 \(2m\) 的序列 \(A\) 满足 \(A\) 中每个元素都是 \(n\) 的因数,且 \(A\) 所有项的乘积不超过 \(n^m\)。答案对 \(998244353\) 取模。

思路:

注意到乘积大于 \(n^m\) 和小于 \(n^m\) 的方案数是一样的,证明如下:

设序列 \(A\) 满足 \(\prod_{i=1}^{2m}A_i<n^m\),设序列 \(B_i=\frac{n}{A_i}\),那么显然有 \(\prod_{i=1}^{2m}B_i>n^m\)\(A\)\(B\) 构成双射,所以相等。

所以我们只需要求出乘积等于 \(n^m\) 的方案数就行了。

我们将 \(n\) 质因数分解,对每一个质因子做多重背包就可以求出答案了。(当然直接写暴力的多重背包就行了)

代码:

#include <bits/stdc++.h>

using namespace std;

const int kMaxM = 2e4 + 5, kM = 998244353;

int n, m, cntfac, cnt, tn, p[kMaxM], c[kMaxM], tot;
long long ans = 1, f[205][kMaxM];

long long fpow(long long a, long long b, long long p, long long r = 1) {
  for (a = (a % p + p) % p; b; b & 1 && (r = r * a % p), a = a * a % p, b >>= 1) {
  }
  return r % p;
}

int main() {
  cin >> n >> m, tn = n;
  for (int i = 2; i * i <= tn; i++) {
    if (tn % i == 0) {
      for (p[++tot] = i; tn % i == 0; tn /= i) {
        c[tot]++;
      }
    }
  }
  if (tn != 1) {
    p[++tot] = tn, c[tot] = 1;
  }
  for (int i = 1; i * i <= n; i++) {
    if (n % i == 0) {
      cntfac++;
      if (i != n / i) cntfac++;
    }
  }
  for (int t = 1; t <= tot; t++) {
    cnt = c[t] * m;
    for (int i = 0; i <= 2 * m; i++) {
      for (int j = 0; j <= cnt + 1; j++) {
        f[i][j] = 0;
      }
    }
    f[0][0] = 1;
    for (int i = 1; i <= 2 * m; i++) {
      for (int k = 0; k <= c[t]; k++) {
        for (int j = k; j <= cnt; j++) {
          f[i][j] = (f[i][j] + f[i - 1][j - k]) % kM;
        }
      }
    }
    ans = 1LL * ans * f[2 * m][cnt] % kM;
  }
  cout << ((fpow(cntfac, 2 * m, kM) - ans + kM) * fpow(2, kM - 2, kM) + ans) % kM;
  return 0;
}

C - 平面点集划分:

题意:

二维平面上有 \(n\) 个点,每个点有两个属性值,第 \(i\) 个点的坐标为 \((x_i,y_i)\) 和两个属性值 \(a_i\)\(b_i\)

将每个点分到 \(A\) 集合或 \(B\) 集合,需要满足对于任意的 \(i\in A,j\in B\),都满足 \(x_i<x_j\)\(y_i>y_j\)

你需要求出 \(\sum_{i\in A}a_i+\sum_{i\in B}b_i\) 的最大值。

思路:

首先将点按照 \(x\) 从小到大排序,\(x\) 相同就按照 \(y\) 从大到小排序,我们按照顺序一个一个考虑放进 \(A\) 还是 \(B\),如果当前元素放进 \(B\),那么肯定满足 \(x_i<x_j\)\(y_i>y_j\);如果当前元素放进 \(A\),那么需要考虑前面的 \(B\) 集合的元素,必须要满足 \(y_i>y_j\),我们记录 \(B\) 集合的 \(y\) 的最大值,如果 \(y_i>y_{\max}\) 那就可以转移。

我们直接设 \(f_{i,j}\) 表示选到第 \(i\) 个元素,\(i\) 之前选 \(B\) 的元素的 \(y\) 的最大值为 \(j\) 的最大值(要离散化一下),直接暴力转移就是 \(O(n^2)\),可以拿到 65pts。

这个东西可以用滚动数组优化,那就可以拿到 80pts。

其实这个东西可以直接用线段树优化,分类 \(y_i>y_{\max}\)\(y_i\le y_{\max}\),这样做是 \(O(n\log n)\) 的,可以拿到 100pts。

代码:

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 4e5 + 5;

int T, n, t[kMaxN], tot;
long long sum, ans, maxn[kMaxN << 2], laz[kMaxN << 2];

struct P {
  int x, y, z;
} p[kMaxN];

void PushUp(int u) {
  maxn[u] = max(maxn[u << 1], maxn[u << 1 | 1]);
}

void Build(int u, int l, int r) {
  maxn[u] = -1e18, laz[u] = 0;
  if (l == r) {
    maxn[u] = l == 0 ? 0 : -1e18;
    return;
  }
  int mid = l + r >> 1;
  Build(u << 1, l, mid), Build(u << 1 | 1, mid + 1, r);
  PushUp(u);
}

void AddTag(int u, long long k) {
  laz[u] += k, maxn[u] += k;
}

void PushDown(int u) {
  if (laz[u]) {
    AddTag(u << 1, laz[u]);
    AddTag(u << 1 | 1, laz[u]);
    laz[u] = 0;
  }
}

void Update(int u, int l, int r, int L, int R, long long k) {
  if (L <= l && r <= R) return AddTag(u, k);
  PushDown(u);
  int mid = l + r >> 1;
  if (L <= mid) Update(u << 1, l, mid, L, R, k);
  if (R > mid) Update(u << 1 | 1, mid + 1, r, L, R, k);
  PushUp(u);
}

long long Query(int u, int l, int r, int L, int R) {
  if (L <= l && r <= R) return maxn[u];
  PushDown(u);
  int mid = l + r >> 1;
  if (R <= mid) return Query(u << 1, l, mid, L, R);
  if (L > mid) return Query(u << 1 | 1, mid + 1, r, L, R);
  return max(Query(u << 1, l, mid, L, R), Query(u << 1 | 1, mid + 1, r, L, R));
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  for (cin >> T; T; T--, ans = sum = tot = 0) {
    cin >> n;
    for (int i = 1, a, b; i <= n; i++) {
      cin >> p[i].x >> p[i].y >> a >> b;
      p[i].z = a - b, sum += b;  // 这里改了一下算贡献的方式
      t[++tot] = p[i].x, t[++tot] = p[i].y;
    }
    sort(t + 1, t + 1 + tot), tot = unique(t + 1, t + 1 + tot) - t - 1;
    for (int i = 1; i <= n; i++) {
      p[i].x = lower_bound(t + 1, t + 1 + tot, p[i].x) - t;
      p[i].y = lower_bound(t + 1, t + 1 + tot, p[i].y) - t;
    }
    sort(p + 1, p + 1 + n, [](P i, P j) { return i.x != j.x ? i.x < j.x : i.y > j.y; });
    Build(1, 0, tot);
    for (int i = 1; i <= n; i++) {
      long long x = Query(1, 0, tot, p[i].y, p[i].y);
      long long y = Query(1, 0, tot, 0, p[i].y - 1);
      if (y > x) {
        Update(1, 0, tot, p[i].y, p[i].y, y - x);
      }
      Update(1, 0, tot, 0, p[i].y - 1, p[i].z);
    }
    for (int i = 0; i <= tot; i++) {
      ans = max(ans, Query(1, 0, tot, i, i));
    }
    cout << sum + ans << '\n';  // 由于前面贡献改了一下,着也要改
  }
  return 0;
}

D - 阻碍:

题意:

给定一个无向图,设 \(1\)\(n\) 的最短路长度为 \(D\),你需要对于每一条边求出这条边的权值 +2 后最短路长度是否为 \(D+1\)

思路:

赛时:

最后 30min,我花 10min 写了一个暴力,然后花 10min 想了一下,然后就没时间了。

代码:

posted @ 2025-08-12 19:44  liruixiong0101  阅读(10)  评论(0)    收藏  举报