多校A層衝刺NOIP2024模擬賽25

ccxswl發表於2024-11-21

非常好的模擬賽,使我的大腦旋轉。

終於不墊底了。


T1

有一張 \(n\) 個點的無向圖,初始沒有邊。

給定 \(m\) 次操作,每次操作給出集合 \(S\)\(T\) ,並將滿足以下條件的邊 \((x,y)\) 存在狀態取反:

  • \(1\le x<y\le n\)
  • \(x\in S,y\in T\)\(x\in T,y\in S\)

\(m\) 次操作後圖中的邊數。

\(n \le 10000,m\le 64\)

出題人給了個什麼 __builtin_popcount 的提示,感覺沒用,好像還誤導我了。不知道為啥直覺上覺得 bool 開一億會爆,發現空間很小就直接切了。

做法就是對於每個點維護周圍的直接連邊,bitset 模擬即可。

複雜度 \(O(\frac{nm}{w})\)

#include <bits/stdc++.h>

using namespace std;

using ubt = long long;

#define vec vector
#define eb emplace_back
#define bg begin
#define mkp make_pair
#define fi first
#define se second

const int inf = 1e9;

const int maxN = 1e4 + 3;

int n, m;
bitset<maxN> b[maxN], ib[maxN];

vec<int> S, T, U;
bitset<maxN> s, t, u;

int main() {
  ifstream cin("a.in");
  ofstream cout("a.out");

  cin >> n >> m;
  for (int p = 1; p <= m; p++) {
    S.clear(), T.clear(), U.clear();
    s.reset(), t.reset(), u.reset();
    for (int i = 0; i < n; i++) {
      char c;
      cin >> c;
      if (c == '0') continue;
      if (c == '1') S.eb(i), s.set(i);
      if (c == '2') T.eb(i), t.set(i);
      if (c == '3') U.eb(i), u.set(i);
    }

    for (int i : S)
      b[i] ^= t ^ u;
    for (int i : T)
      b[i] ^= s ^ u;
    for (int i : U)
      b[i] ^= u ^ s ^ t;

//    cerr << "T: " << t << '\n';
//    cerr << "S: " << s << '\n';
//    for (int i = 0; i < n; i++)
//      cerr << i + 1 << ": " << b[i] << '\n';
  }

  int ans = 0;
  for (int i = 0; i < n; i++)
    b[i].reset(i), ans += b[i].count();
  cout << ans / 2 << '\n';
}

貌似 DrRatio 用到提示了。


T2

昨天剛做皇后遊戲,今天直接考貪心了,感覺很厲害。賽時過了。

給定長為 \(n\) 的序列 \(a_1,a_2,\dots,a_n\)​ 和 \(m\) 次操作,每次操作有引數 \((t,x,y)\)

  • \(t=1\) 表示修改 \(a_x^′=y\)
  • \(t=2\) 表示修改 \(a_x^′=a_x+y\)
  • \(t=3\) 表示修改 \(a_x^′=a_x\times y\)

從中選擇至多 \(k\) 個不同的操作並以任意順序執行,最大化最終的 \(\prod _{i=1}^n a_i\),答案對 \(1e9+7\) 取模。

發現所有操作都應該是賦值在前,加法隨後,乘法最後。賦值只有最大的有用,可以把賦值看成加上 \(y-a_i\),因為加法具有交換律,所以正確。

現在只有加法和乘法了。因為求的是 \(\prod\limits_i (a_i+\sum\limits_j x_j)\times \prod\limits_j y_j\)\(x\) 為第 \(i\) 個數的加法操作,\(y\) 是乘法操作。所以所有的乘法操作都是在外面的,乘法具有交換律,所以同一個數、不同數乘法和乘法之間的優先順序都是大的優先。就可以把乘法拿出來單獨考慮了。

對於同一個數的加法操作顯然是大的先。

問題現在在於不同數的加法操作優先順序,還有加法和乘法操作之間的優先順序。

因為你剛做完皇后遊戲,所以考慮類似臨項交換的方法。

\(i\) 的一個加法,比 \(j\) 的優:

\[\text{oth}\times(a_i+x_i)\times a_j < \text{oth}\times a_i \times (a_j + x_j) \]

\[\frac{x_i}{a_i}<\frac{x_j}{a_j} \]

\(i\) 的一個加法,比乘法優:

\[\text{oth}\times (a_i+x_i)<\text{oth}\times a_i \times b \]

\[1+\frac{x_i}{a_i}<b \]

把所有加法操作扔到優先佇列裡,每次看最優的加法操作比不比最劣的乘法操作優,貪心的選就行了。

PS:被 hack 了,不過不是大問題,實現不太精細,有個地方爆 long long 了。

#include <bits/stdc++.h>

using namespace std;

#define int long long
using ubt = long long;

#define vec vector
#define eb emplace_back
#define bg begin
#define mkp make_pair
#define fi first
#define se second

const int inf = 1e9;

const int maxN = 1e5 + 7, mod = 1e9 + 7;

int ksm(int a, int b = mod - 2) {
  a %= mod;
  int r = 1;
  while (b) {
    (b & 1) && (r = r * a % mod);
    a = a * a % mod;
    b >>= 1;
  }
  return r;
}

int n, m;

struct node {
  int v;
  vec<int> add;
  int co;
  friend bool operator < (const node &A, const node &B) {
    auto a = A.add.back(), b = B.add.back();
    return 1. * a * B.v < 1. * b * A.v;
  }
} a[maxN];

int mul[maxN], kp[maxN], ml;

priority_queue<node> Q;

signed main() {
  ifstream cin("b.in");
  ofstream cout("b.out");

  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    cin >> a[i].v;

  for (int i = 1; i <= m; i++) {
    int t, x, y;
    cin >> t >> x >> y;
    if (t == 1)
      if (y > a[x].co)
        a[x].co = y;
    if (t == 2)
      a[x].add.eb(y);
    if (t == 3)
      mul[++ml] = y;
  }
  
  sort(mul + 1, mul + ml + 1, greater<int> ());
  mul[0] = 1;
  for (int i = 0; i <= ml; i++) kp[i] = mul[i];
  for (int i = 1; i <= ml; i++)
    mul[i] = mul[i - 1] * mul[i] % mod;

  for (int i = 1; i <= n; i++) {
    a[i].add.eb(-1);
    if (a[i].co > a[i].v)
      a[i].add.eb(a[i].co - a[i].v);
    sort(a[i].add.bg(), a[i].add.end());
  }

  for (int i = 1; i <= n; i++)
    Q.emplace(a[i]);

  int cnt = 0;
  int ans = 1;
  auto calc = [&](int p, int tot) {
    int num = tot - p;
    if (num > ml) num = ml;
    if (num < 0) num = 0;
    return mul[num];
  };
  for (int i = 1; i <= n; i++) (ans *= a[i].v) %= mod;
  for (int k = 0; k <= m; k++) {
    while (cnt < k) {
      auto t = Q.top();
      auto ad = t.add.back();
      if (ad == -1) break;
      if (cnt + 1 + ml <= k || t.v * (kp[k - cnt] - 1) < ad) {
        Q.pop();
        cnt++;
        ans = ans * ksm(t.v) % mod * (t.v + ad) % mod;
        t.v += ad;
        t.add.pop_back();
        Q.emplace(t);
      } else break;
    }

    auto res = ans * calc(cnt, k) % mod;
    cout << res << ' ';
  }
  cout << '\n';
}



T4

給定長為 \(n\) 的字串 \(s\)(下標從 \(1\) 開始),其僅包含前 \(k\) 種小寫字母,共 \(m\) 種操作,為以下兩種之一:

  • \(∀i\in [l,r]\) 修改 \(s_i\)​ 為其後 \(c\) 個字母(這裡認為第 \(k\) 個小寫字母的後一個字母為 \(a\)

  • 對於前 \(k\) 種小寫字母的一個排列 \(t\),在 \(s[l,r]\) 中插入若干字元使得其為若干個 \(t\) 拼接的結果

    求最小的 \(\frac{∣s^′∣}{k}\)\(s^′\) 為插入字元後的字串),注意並不真的在 \(s\) 中插入。

賽後看到線段樹就瞬間明白了。

試了一下各種引用,拿了最優解。

感覺挺智慧。將每個字元按 \(t\) 中位置編號,即求其極長遞增段的個數,也即相鄰兩個字元逆序的個數。

#include <bits/stdc++.h>

using namespace std;

#define vec vector

using Ar = array<array<int, 10>, 10>;

const int maxN = 2e5 + 7;

int n, m, k;
string s;

#define id(x) \
  ((x) >= k ? (x) - k : (x))

int tg[maxN * 2];
struct dat {
  int l, r;
  Ar f;
  friend dat operator + (const dat &A, const dat &B) {
    dat res;
    res.l = A.l, res.r = B.r;
    for (int i = 0; i < k; i++)
      for (int j = 0; j < k; j++)
        res.f[i][j] = A.f[i][j] + B.f[i][j];
    res.f[A.r][B.l]++;
    return res;
  }
  void operator += (const int &T) {
    Ar tmp;
    for (int i = 0; i < k; i++)
      for (int j = 0; j < k; j++)
        tmp[id(i + T)][id(j + T)] = f[i][j];
    f = tmp;
    l = id(l + T), r = id(r + T);
  }
} t[maxN * 2];
#define ls (mid << 1)
#define rs (mid << 1 | 1)
void build(int l, int r, int p) {
  if (l == r) {
    t[p].l = t[p].r = s[l] - 'a';
    return;
  }
  int mid = (l + r) >> 1;
  build(l, mid, ls), build(mid + 1, r, rs);
  t[p] = t[ls] + t[rs];
}
inline void make(const int &p, int &lz) {
  t[p] += lz;
  tg[p] = id(tg[p] + lz);
}
inline void down(int &p, int &mid) {
  if (!tg[p]) return;
  make(ls, tg[p]);
  make(rs, tg[p]);
  tg[p] = 0;
}
void change(int &L, int &R, int &v, int p, int l, int r) {
  if (L <= l && r <= R) return make(p, v);
  int mid = (l + r) >> 1;
  down(p, mid);
  if (L <= mid)
    change(L, R, v, ls, l, mid);
  if (R > mid)
    change(L, R, v, rs, mid + 1, r);
  t[p] = t[ls] + t[rs];
}
dat ask(int &L, int &R, int p, int l, int r) {
  if (L <= l && r <= R) return t[p];
  int mid = (l + r) >> 1;
  down(p, mid);
  if (R <= mid)
    return ask(L, R, ls, l, mid);
  if (L > mid)
    return ask(L, R, rs, mid + 1, r);
  return ask(L, R, ls, l, mid) + ask(L, R, rs, mid + 1, r);
}

int main() {
  ifstream cin("d.in");
  ofstream cout("d.out");

  cin >> n >> m >> k;
  cin >> s;
  s = '!' + s;
  build(1, n, 1);

  while (m--) {
    int op, l, r;
    cin >> op >> l >> r;
    if (op == 1) {
      int c;
      cin >> c;
      change(l, r, c, 1, 1, n);
    }
    if (op == 2) {
      string g;
      cin >> g;
      auto &&res = ask(l, r, 1, 1, n);
      int ans = 1;
      for (int i = 0; i < k; i++)
        for (int j = i, x = g[i] - 'a'; j < k; j++) {
          int y = g[j] - 'a';
          ans += res.f[y][x];
        }
      cout << ans << '\n';
    }
  }
}

相關文章