代码源集训总结(1=21Days)

Day 1(贪心构造交互思维)

唐诗 hhc 上午只做出来一个题。果然还是要增大一些思维广度对吗(成功 miss 了所有题的思路)?

A CodechefMINORPATH Minimum OR Path

schabig 按位贪心。锁定某一位为 0,我们可以封锁一些节点。如果在封锁之后仍然可以从 \(1\)\(N\),则保持这些点封锁,否则重新开放这些点(但是不会开放之前保持封锁的的)。

坑点 小心开放节点的时候不要开放一个之前封锁了的节点。
点击查看代码
#include <bits/stdc++.h>
using namespace std;

int t, n, arr[500050], av[500050];

int main() {
  for(cin >> t; t--; ) {
    cin >> n;
    for(int i = 1; i <= n; i++) {
      cin >> arr[i], av[i] = 1;
    }
    int tmp = 1;
    for(int i = 1; i <= n; i++) {
      if(i <= tmp) {
        tmp = max(tmp, i + arr[i]);
      }
    }
    if(tmp < n) {
      cout << -1 << '\n';
      continue;
    }
    int ans = 0;
    for(int j = 20; j >= 0; j--) {
      vector<int> rvt;
      for(int i = 1; i <= n; i++) {
        if((arr[i] >> j) & 1) {
          rvt.push_back(i), av[i]--;
        }
        //cout << av[i];
      }
      //cout << '\n';
      int r = 1;
      for(int i = 1; i <= n; i++) {
        if(i <= r && av[i] == 1) {
          r = max(r, i + arr[i]);
        }
      }
      if(r < n || !av[n]) {
        ans |= (1 << j);
        for(auto j : rvt) {
          av[j]++;
        }
      }
    }
    cout << ans << '\n';
  }
  return 0;
}

B Codeforces1763C Another Array Problem

(hhc)是唐。

显然可以将一个长度至少为 \(2\) 的区间全部赋值成 \(0\) 的,然后最大值就可以开始扩散了。所以:

\[ans = \begin{cases} \text{(max element in the array)} \times n & n \ge 4 \\ \max(a_1 + a_2, 2 \operatorname{abs}(a_1 - a_2)) & n = 2 \\ \text{(max element in the array)} \times n & a_2 \le \max(a_1, a_3)\\ \max(a_1 + a_2 + a_3, 3 \max(a_2 - a_1, a_2 - a_3, \operatorname{abs}(a_1 - a_3), a_1, a_3)) & otherwise \end{cases} \]

最后一种情况可以分类讨论得到。

点击查看代码

中间那个长长的东西是最后一种情况的分类讨论。

#include <bits/stdc++.h>
#define int long long
using namespace std;

int t, n, arr[200020];

signed main() {
  for(cin >> t; t--; ) {
    cin >> n;
    for(int i = 1; i <= n; i++) {
      cin >> arr[i];
    }
    if(n == 2) {
      cout << max(arr[1] + arr[2], 2 * abs(arr[1] - arr[2])) << '\n';
      continue;
    }
    if(n == 3) {
      if(arr[2] > arr[1] && arr[2] > arr[3]) {
        cout << max({3 * arr[1], 3 * arr[3], arr[1] + arr[2] + arr[3], 3 * abs(arr[3] - arr[2]), 3 * abs(arr[1] - arr[2])}) << '\n';
      }else {
        cout << max(arr[1], arr[3]) * 3 << '\n';
      }
      continue;
    }
    cout << *max_element(arr + 1, arr + n + 1) * n << '\n';
  }
  return 0;
}

C CodeForces2124E Make It Zero

那个 \(1 \le s \le 17\) 是用来诈骗的。真实情况是 \(1 \le s \le 2\)

当且仅当所有数的和为奇数或是有一个数严格大于其他所有数的和事是无解的。否则,显然可以每次对两个数减一。

首先考虑 \(n = 3\),这时:

如果第一个数或第三个数为其他两个数的和,一次操作把整个数组减完即可;

否则,先把第一个数和第三个数同时减去一个值使得这两个数的和等于第二个数;然后,把第二个数与第一个数和第三个数“分尸”(先把第二个数中第一个数的部分减了,对于第三个数同理)。可以发现分尸需要两次操作,但是这个方案下的第一和第二次操作显然是可以合并的。于是操作次数就只有 \(2\) 了。

对于 \(n > 3\) 的情况,是同理的。

只需要找到第一个前缀和大于总和的位置 \(p\),然后把 \(\sum \limits_{i = 1}^{p - 1} a_i\) 作为第一个数,\(a_p\) 作为第二个数,\(\sum \limits_{i = p + 1}^n a_i\) 作为第三个数,然后使用上面的方案即可。

注意“第一个数”和“第三个数”内部的分担。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

int t, n, arr[50050];
vector<vector<int> > ans;

signed main() {
  for(cin >> t; t--; ) {
    ans.clear();
    cin >> n;
    for(int i = 1; i <= n; i++) {
      cin >> arr[i];
    }
    int tmp = accumulate(arr + 1, arr + n + 1, 0ll);
    if(tmp % 2 || *max_element(arr + 1, arr + n + 1) > tmp / 2) {
      cout << -1 << '\n';
      continue;
    }
    int p = 1, ps = 0;
    for(; p <= n; p++) {
      ps += arr[p];
      if(ps >= tmp / 2) {
        break;
      }
    }
    if(accumulate(arr + 1, arr + p + 1, 0ll) == accumulate(arr + p + 1, arr + n + 1, 0ll)) {
      cout << 1 << '\n';
      for(int i = 1; i <= n; i++) {
        cout << arr[i] << ' ';
      }
      cout << '\n';
      continue;
    }
    if(accumulate(arr + 1, arr + p, 0ll) + accumulate(arr + p + 1, arr + n + 1, 0ll) > arr[p]) {
      int xd = (accumulate(arr + 1, arr + p, 0ll) + accumulate(arr + p + 1, arr + n + 1, 0ll) - arr[p]) / 2, tmp = xd;
      vector<int> tans;
      for(int i = 1; i < p; i++) {
        int ttmp = min(tmp, arr[i]);
        tans.push_back(ttmp);
        tmp -= ttmp, arr[i] -= ttmp;
      }
      tans.push_back(0);
      tmp = xd;
      for(int i = p + 1; i <= n; i++) {
        int ttmp = min(tmp, arr[i]);
        tans.push_back(ttmp);
        tmp -= ttmp, arr[i] -= ttmp;
      }
      ans.push_back(tans);
    }
    if(!ans.size()) {
      ans.resize(1);
      ans.back().resize(n);
    }
    for(int i = 1; i <= n; i++) {
      if(i < p) {
        ans.back()[i - 1] += arr[i];
      }
      if(i == p) {
        ans.back()[i - 1] += accumulate(arr + 1, arr + p, 0ll);
      }
    }
    ans.push_back(vector<int>());
    for(int i = 1; i <= n; i++) {
      if(i < p) {
        ans.back().push_back(0);
      }
      if(i == p) {
        ans.back().push_back(accumulate(arr + p + 1, arr + n + 1, 0ll));
      }
      if(i > p) {
        ans.back().push_back(arr[i]);
      }
    }
    cout << ans.size() << '\n';
    for(auto i : ans) {
      for(auto j : i) {
        cout << j << ' ';
      }
      cout << '\n';
    }
  }
  return 0;
}

E Gym105484B Birthday Gift

考虑定义 \(B_i = A_i \oplus (i + 1 \bmod 2)\),于是原本的操作变成了:

选择一个相邻的 \(0\)\(1\),把这两个数的存在删除(也就是说数组剩下的部分会拼接起来)

于是,答案为 \(B\) 中两种字符出现次数的差。

至于 \(2\) 吗你懂的。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int t;
string s;

int main() {
  for(cin >> t; t--; ) {
    cin >> s;
    for(int i = 0; i < s.size(); i++) {
      if(i % 2 && s[i] != '2') {
        s[i] = '0' + (s[i] == '0');
      }
    }
    int c0 = count(s.begin(), s.end(), '0'), c1 = count(s.begin(), s.end(), '1');
    for(auto i : s) {
      if(i == '2') {
        if(c0 <= c1) {
          c0++;
        }else {
          c1++;
        }
      }
    }
    cout << abs(c0 - c1) << '\n';
  }
  return 0;
}

G LuoguP4064 加法

看到最小值最大,考虑二分答案。然后:

对于每一个区间,我们能不加就不加;

对于每一个数,我们在把这个数加到符合要求时,一次一次选右端点最大的区间。写个那啥搞一下即可。

Day2A. 模拟赛

开局秒了 A 题,然后卡住了...

C 没有任何思路。KMP 暴力走人。

B 想到一个 dp。假掉了。

D 只能骗到 \(2\) 分。

然后光荣地走人了

Day2B. 数据结构(1)

A Codeforces1474C Array Destruction

考虑进行第一次操作。如果第一次操作没删最大的元素,那么这个元素就删不了了。因此,可以在第一次操作中枚举删除最大的元素和某个元素,然后写个 set 判断一下即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int t, n, arr[2020], f;
pair<int, int> ans[2020];
multiset<int> s;

void check(int x) {
  ans[1] = {arr[n], arr[x]};
  int sm = arr[n], k = 1;
  s.clear();
  for(int i = 1; i < n; i++) {
    if(i != x) {
      s.insert(arr[i]);
    }
  }
  for(; s.size(); ) {
    int tmp = *prev(s.end());
    s.erase(prev(s.end()));
    if(s.find(sm - tmp) == s.end()) {
      return ;
    }
    s.erase(s.find(sm - tmp));
    ans[++k] = {tmp, sm - tmp};
    sm = tmp;
  }
  cout << "YES" << '\n' << arr[n] + arr[x] << '\n', f = 1;
  for(int i = 1; i <= k; i++) {
    cout << ans[i].first << ' ' << ans[i].second << '\n';
  }
}

int main() {
  for(cin >> t; t--; ) {
    cin >> n;
    n <<= 1, f = 0;
    for(int i = 1; i <= n; i++) {
      cin >> arr[i];
    }
    sort(arr + 1, arr + n + 1);
    for(int i = 1; i < n; i++) {
      check(i);
      if(f) {
        break;
      }
    }
    if(!f) {
      cout << "NO" << '\n';
    }
  }
  return 0;
}

B Codeforces1237D Balanced Playlist

O5-3 实在太烧辣!

首先判断一下答案是否是一坨 \(-1\)。这是很容易的。

然后,答案一定只会到达 \(3n\):第一轮听到最酷的,第二轮听到最不酷的然后砸死音乐。

所以可以把数组延长成原来的 \(3\) 倍长度,然后在这上面用单调栈建 BMS 父项树,但是连到比它后面更大的元素上(\(3n+1\) 为默认为一个特别大的值,反正也不会搞到那儿)。

随之,就可以判断到父项前是否会砸死音乐,然后就可以计算了。

开个那啥维护一下即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int n, arr[300030], d[1200012], fa[300030], fin[300030];

void build(int id = 1, int l = 1, int r = 3 * n) {
  if(l == r) {
    d[id] = arr[l];
    return ;
  }
  int mid = (l + r) >> 1;
  build(id * 2, l, mid);
  build(id * 2 + 1, mid + 1, r);
  d[id] = min(d[id * 2], d[id * 2 + 1]);
}

int query(int ql, int qr, int id = 1, int l = 1, int r = 3 * n) {
  if(r < ql || qr < l) {
    return 1232131211;
  }
  if(ql <= l && r <= qr) {
    return d[id];
  }
  int mid = (l + r) >> 1;
  return min(query(ql, qr, id * 2, l, mid), query(ql, qr, id * 2 + 1, mid + 1, r));
}

int queryx(int ql, int qr, int v, int id = 1, int l = 1, int r = 3 * n) {
  //cout << ql << ' ' << qr << ' ' << v << ' ' << id << ' ' << l << ' ' << r << '\n';
  if(l == r) {
    return l;
  }
  int mid = (l + r) >> 1;
  //cout << query(ql, qr, id * 2, l, mid) << '\n';
  if(query(ql, qr, id * 2, l, mid) < (v + 1) / 2) {
    return queryx(ql, qr, v, id * 2, l, mid);
  }else {
    return queryx(ql, qr, v, id * 2 + 1, mid + 1, r);
  }
}

int main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
    arr[i + n] = arr[i + n + n] = arr[i];
  }
  build();
  vector<int> v;
  for(int i = 1; i <= 3 * n; i++) {
    for(; v.size() && arr[v.back()] < arr[i]; v.pop_back()) fa[v.back()] = i;
    v.push_back(i);
  }
  for(; v.size(); v.pop_back()) fa[v.back()] = 3 * n + 1;
  if(*min_element(arr + 1, arr + n + 1) >= (*max_element(arr + 1, arr + n + 1) + 1) / 2) {
    for(int i = 1; i <= n; i++) {
      cout << -1 << ' ';  
    }
    cout << '\n';
    return 0;
  }
  for(int i = 3 * n; i >= 1; i--) {
    if(fa[i]) {
      if(fa[i] - i > 1 && query(i + 1, fa[i] - 1) < (arr[i] + 1) / 2) {
        fin[i] = queryx(i + 1, fa[i] - 1, arr[i]);
      }else {
        fin[i] = fin[fa[i]];
      }
    }
    //cout << fin[i] << ' ' << fin[fa[i]] << ' ' << fa[i] << '\n';
  }
  //cout << '\n';
  //queryx(2, 12, 11);
  for(int i = 1; i <= n; i++) {
    cout << fin[i] - i << ' ';
  }
  cout << '\n';
  return 0;
}

C Codeforces1924B Space Harbour

我们发现加入一个港口后:

上一个港口到这个港口的船的花费减少了一个定值;

这个港口的船现在花费为 \(0\) 了;

后面的港口的花费则是减少了一个等差数列。

所以我们的线段树需要支持区间加等差数列和区间查询和。直接维护差分数列即可。

F LuoguP5268 一个简单的询问

首先差分转化一下,把一个查询拆成四个查询。然后就可以莫队水过去了。

Day3. 数据结构(2)

A Codeforces1208D Restore Permutation

观察所有 \(s_i\)\(0\) 的位置,这些位置由于前面没有任何一个比它小的,所以是前缀最小值。显然的,最后一个前缀最小值一定是 \(1\),于是我们就获知了一个数,把这个数删掉,重复上述过程即可。

线段树维护一下最小值。

坑点
  1. \(i\) 次获知的数一定是 \(i\)
  2. 获知了一个数后记得把这个数“删掉”,也就是修改成 \(+\infty\)
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, s[200020], d[800080], t[800080], per[800080];

void pushdn(int id) {
  d[id * 2] += t[id], d[id * 2 + 1] += t[id];
  t[id * 2] += t[id], t[id * 2 + 1] += t[id];
  t[id] = 0;
}

int build(int id = 1, int l = 1, int r = n) {
  if(l == r) {
    return d[id] = s[l];
  }
  int mid = (l + r) >> 1;
  return d[id] = min(build(id * 2, l, mid), build(id * 2 + 1, mid + 1, r));
}

void modify(int ml, int mr, int v, int id = 1, int l = 1, int r = n) {
  if(r < ml || mr < l) {
    return ;
  }
  if(ml <= l && r <= mr) {
    d[id] -= v, t[id] -= v;
    return ;
  }
  pushdn(id);
  int mid = (l + r) >> 1;
  modify(ml, mr, v, id * 2, l, mid);
  modify(ml, mr, v, id * 2 + 1, mid + 1, r);
  d[id] = min(d[id * 2], d[id * 2 + 1]);
}

int query(int id = 1, int l = 1, int r = n) {
  //cout << id << ' ' << l << ' ' << r << ' ' << d[id] << ' ' << d[id * 2 + 1] << '\n';
  if(l == r) {
    d[id] = 111111111111111;
    return l;
  }
  int mid = (l + r) >> 1;
  pushdn(id);
  if(d[id * 2 + 1] == 0) {
    int tmp = query(id * 2 + 1, mid + 1, r);
    d[id] = min(d[id * 2], d[id * 2 + 1]);
    return tmp;
  }else {
    int tmp = query(id * 2, l, mid);
    d[id] = min(d[id * 2], d[id * 2 + 1]);
    return tmp;
  }
}

signed main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> s[i];
  }
  build();
  for(int i = 1; i <= n; i++) {
    int tmp = query();
    per[tmp] = i;
    modify(tmp + 1, n, i);
  }
  for(int i = 1; i <= n; i++) {
    cout << per[i] << ' ';
  }
  cout << '\n';
  return 0;
}

B Codeforces1690G Count the Trains

火车太难维护,所以我们放弃维护火车,转而维护火车头。

把一个车厢变慢可能发生如下情况:

  1. 这个车厢比它原本的火车头还要慢一些,导致它独立成为一个新的火车头;

  2. 这个车厢变慢之后挡住了后面的一些火车,致使这些火车都成为了一个火车。

开一个 set 维护即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int t, n, m, a[100010], k, d;
set<int> hd;

int main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  for(cin >> t; t--; ) {
    cin >> n >> m;
    hd.clear();
    hd.insert(1);
    for(int i = 1; i <= n; i++) {
      cin >> a[i];
      if(a[i] < a[*prev(hd.end())]) {
        hd.insert(i);
      }
    }
    //cout << hd.size() << '\n';
    for(; m--; ) {
      cin >> k >> d;
      a[k] -= d;
      auto tmp = hd.lower_bound(k);
      if(tmp == hd.begin() || a[*prev(tmp)] > a[k]) {
        hd.insert(k);
        for(; ; ) {
          auto ttmp = hd.upper_bound(k);
          if(ttmp == hd.end()) {
            break;
          }
          if(a[*ttmp] >= a[k]) {
            hd.erase(ttmp);
          }else {
            break;
          }
        }
      }
      cout << hd.size() << ' ';
    }
    cout << '\n';
  }
  return 0;
}

C Codeforces1913D Array Collapse

首先可以发现操作的长度只多为 \(2\)

然后,设计状态 \(dp_i\)\(i\) 的前缀的答案数。为了做到不重不漏,只能统计后缀最大值的 \(dp\) 之和。

考察转移:首先,\(dp_{i - 1} \to dp_i\)。直接加上最后一个数即可。

然后,考虑删掉了某些数的情况,我们发现 \(dp_i \to dp_{fa_i}\),其中 \(fa_i\) 为第一个在 \(i\) 后面且 \(p_i > p_j\) 的数。

\(fa\) 预处理出来之后扩散收集混合转移就可以了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

const int kMod = 998244353;

i64 t, n, p[300030], fa[300030], dp[300030], sb[300030], ans;
vector<i64> stk;

int main() {
  for(cin >> t; t--; ) {
    cin >> n;
    p[0] = p[n + 1] = 1145141919810ll, fa[0] = dp[0] = sb[0] = 0;
    for(int i = 1; i <= n; i++) {
      cin >> p[i];
      fa[i] = dp[i] = sb[i] = 0;
    }
    int mn = min_element(p + 1, p + n + 1) - p;
    for(int i = 0; i <= n; i++) {
      for(; stk.size() && p[stk.back()] > p[i]; stk.pop_back()) {
        fa[stk.back()] = i;
      }
      stk.push_back(i);
    }
    sb[0] = 1;
    sb[fa[0]] += sb[0];
    ans = 0;
    for(int i = 1; i <= n; i++) {
      dp[i] = (dp[i] + dp[i - 1]) % kMod;
      int act = (dp[i] + sb[i]) % kMod;
      if(!fa[i]) {
        ans = (ans + act) % kMod;
      }
      sb[fa[i]] = (sb[fa[i]] + act + sb[i]) % kMod;
      dp[i + 1] = (dp[i + 1] + act) % kMod;
      dp[fa[i]] = (dp[fa[i]] - act + kMod) % kMod;
      //cout << i << ' ' << dp[i] << ' ' << sb[i] << ' ' << act << ' ' << ans << ' ' << fa[i] << '\n';
    }
    cout << ans << '\n';
    stk.clear();
  }
  return 0;
}
/*
*/

D Codeforces2000H Ksyusha and the Loaded Set

开一个 set 维护连续段,开一个 map 里面套一个 set 维护长度正好为 \(k\) 的连续段,最后开一个线段树维护区间最小值就可以了。(怎么感觉这么复杂呢?)

E LuoguP7706 文文的摄影布置

Madore's \(\xcancel{\psi}\),Veblen's \(\xcancel{\varphi}\) and Rathjen's \(\xcancel{\Psi}\)

首先定义 \(\varphi(i, j, k) = A_i - B_j + A_k\),其中 \(i < j < k\)

然后,我们有 \(\Psi(i, j) = \min \limits_{i < k < j} (\varphi(i, k, j))\)。易知区间 \((l, r)\) 的答案就是 \(\min \limits_{l \le x < y \le r} (\Psi(x, y))\)

考虑搞一个线段树维护 \(\Psi\),可以发现答案要么在左子树要么在右子树,还有可能是横跨左右子树。所以定义 \(\varepsilon(x, y) = \max \limits_{x \le a < b \le y} (A_a - B_b), \zeta(x, y) = \max \limits_{x \le a < b \le y} (A_b - B_a)\)

为了维护这两坨,还需要维护 \(\alpha(l, r) = \max \limits_{l \le i \le r} A_i, \beta(l, r) = \min \limits_{l \le i \le r} B_i\)

然后就可以开心快乐地过掉这个题了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

struct dat {
  int am, bm, lm, rm, ans;
  
  dat operator ^(const dat &b) const {
    return {max(am, b.am), min(bm, b.bm), max({lm, b.lm, am - b.bm}), max({rm, b.rm, b.am - bm}), max({ans, b.ans, lm + b.am, am + b.rm})};
  }
}d[2000020];

int n, m, a[500050], b[500050], op, x, y;

void build(int id = 1, int l = 1, int r = n) {
  if(l == r) {
    d[id] = {a[l], b[l], -1145141919, -1145141919, -1145141919};
    return ;
  }
  int mid = (l + r) >> 1;
  build(id * 2, l, mid);
  build(id * 2 + 1, mid + 1, r);
  d[id] = d[id * 2] ^ d[id * 2 + 1];
}

void modifya(int x, int v, int id = 1, int l = 1, int r = n) {
  if(l == r) {
    a[x] = v;
    d[id] = {a[l], b[l], -1145141919, -1145141919, -1145141919};
    return ;
  }
  int mid = (l + r) >> 1;
  if(x <= mid) modifya(x, v, id * 2, l, mid);
  else modifya(x, v, id * 2 + 1, mid + 1, r);
  d[id] = d[id * 2] ^ d[id * 2 + 1];
}

void modifyb(int x, int v, int id = 1, int l = 1, int r = n) {
  if(l == r) {
    b[x] = v;
    d[id] = {a[l], b[l], -1145141919, -1145141919, -1145141919};
    return ;
  }
  int mid = (l + r) >> 1;
  if(x <= mid) modifyb(x, v, id * 2, l, mid);
  else modifyb(x, v, id * 2 + 1, mid + 1, r);
  d[id] = d[id * 2] ^ d[id * 2 + 1];
}

dat query(int ql, int qr, int id = 1, int l = 1, int r = n) {
  if(r < ql || qr < l) {
    return {-1145141919, 1145141919, -1145141919, -1145141919, -1145141919};
  }
  if(ql <= l && r <= qr) {
    return d[id];
  }
  int mid = (l + r) >> 1;
  return query(ql, qr, id * 2, l, mid) ^ query(ql, qr, id * 2 + 1, mid + 1, r);
}

signed main() {
  cin >> n >> m;
  for(int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for(int i = 1; i <= n; i++) {
    cin >> b[i];
  }
  build();
  for(; m--; ) {
    cin >> op >> x >> y;
    if(op == 1) {
      modifya(x, y);
    }
    if(op == 2) {
      modifyb(x, y);
    }
    if(op == 3) {
      cout << query(x, y).ans << '\n';
    }
  }
  return 0;
}

考虑对每一个数维护上一个与它和为 \(w\) 的的数。

然而,这样子可以搞到一坨,然后你就炸了。

不过,我们是有方法的!

考虑到每一个数对答案做出的贡献,我们发现,如果一个数的前驱比上一个与它相同的数还要前面,这个数肯定造成不了贡献,于是就可以将其前驱设为 \(0\)

这样每次修改只需修改五个数的前驱,线段树 + set 维护一下即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int n, m, w, arr[500050], op, x, y, d[2000020];
set<int> st[500050];

int get(int x) {
  int ps = *prev(st[arr[x]].lower_bound(x)), pa = *prev(st[w - arr[x]].lower_bound(x));
  return (ps > pa? 0 : pa);
}

void build(int id = 1, int l = 1, int r = n) {
  if(l == r) {
    d[id] = get(l);
    return ;
  }
  int mid = (l + r) >> 1;
  build(id * 2, l, mid);
  build(id * 2 + 1, mid + 1, r);
  d[id] = max(d[id * 2], d[id * 2 + 1]);
}

bool modify(int x, int id = 1, int l = 1, int r = n) {
  if(l == r) {
    d[id] = get(x);
    return true;
  }
  int mid = (l + r) >> 1;
  if(x <= mid) modify(x, id * 2, l, mid);
  else modify(x, id * 2 + 1, mid + 1, r);
  d[id] = max(d[id * 2], d[id * 2 + 1]);
  return true;
}

void modifya(int x, int v) {
  int p1 = x;
  int p2 = (st[arr[x]].upper_bound(x) == st[arr[x]].end()? 0 : *st[arr[x]].upper_bound(x));
  int p3 = (st[w - arr[x]].upper_bound(x) == st[w - arr[x]].end()? 0 : *st[w - arr[x]].upper_bound(x));
  st[arr[x]].erase(x);
  arr[x] = v;
  st[arr[x]].insert(x);
  int p4 = (st[arr[x]].upper_bound(x) == st[arr[x]].end()? 0 : *st[arr[x]].upper_bound(x));
  int p5 = (st[w - arr[x]].upper_bound(x) == st[w - arr[x]].end()? 0 : *st[w - arr[x]].upper_bound(x));
  p1 && modify(p1), p2 && modify(p2), p3 && modify(p3), p4 && modify(p4), p5 && modify(p5);
}

bool query(int ql, int qr, int id = 1, int l = 1, int r = n) {
  if(r < ql || qr < l) {
    return false;
  }
  if(ql <= l && r <= qr) {
    return d[id] >= ql;
  }
  int mid = (l + r) >> 1;
  return query(ql, qr, id * 2, l, mid)|| query(ql, qr, id * 2 + 1, mid + 1, r);
}

int main() {
  cin >> n >> m >> w;
  for(int i = 0; i <= w; i++) {
    st[i].insert(0);
  }
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
    st[arr[i]].insert(i);
  }
  build();
  int lastans = 0;
  for(; m--; ) {
    int op = 0, x = 0, y = 0;
    cin >> op >> x >> y;
    if(op == 1) {
      modifya(x, y);
    }else {
      x ^= lastans, y ^= lastans;
      //cout << x << ' ' << y << ' '; 
      if(query(x, y)) {
        lastans++, cout << "Yes" << '\n';
      }else {
        cout << "No" << '\n';
      }
    }
  }
  return 0;
}

Day5A. 模拟赛

秒 A,然后 C 假掉了。

B 是个 schabig 贪心,枚举最大值即可。

然后停机。

Day5B. 数据结构(3)

A Codeforces 459D Pashmak and Parmida's Problem

预处理需要的 \(f\) 的值然后拿个那啥维护一下即可。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, arr[1000010], pre[1000010], suf[1000010], tmp[1000010], nn, cnt[1000010], c[1000010];

void add(int x, int v) {
  if(x > n) return ;
  c[x] += v, add(x + (x & -x), v);
}

int query(int x) {
  return (x? query(x - (x & -x)) + c[x] : 0);
}

signed main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
    tmp[++nn] = arr[i];
  }
  sort(tmp + 1, tmp + nn + 1);
  nn = unique(tmp + 1, tmp + nn + 1) - tmp - 1;
  for(int i = 1; i <= n; i++) {
    arr[i] = lower_bound(tmp + 1, tmp + nn + 1, arr[i]) - tmp;
  }
  for(int i = 1; i <= n; i++) {
    pre[i] = ++cnt[arr[i]];
  }
  fill(cnt + 1, cnt + nn + 1, 0);
  for(int i = n; i >= 1; i--) {
    suf[i] = ++cnt[arr[i]];
    add(suf[i], 1);
  }
  int ans = 0;
  for(int i = 1; i <= n; i++) {
    add(suf[i], -1);
    ans += query(pre[i] - 1);
  }
  cout << ans << '\n';
  return 0;
}

B Codeforces1849E Max to the Right of Min

开个单调栈什么的维护一下 \(\max\) 出现的位置和 \(\min\) 出现的位置然后搞一下即可。

Day6. 字符串

A Codeforces176B Word Cut

可以发现题目中那个操作其实就是循环移位。然后就可以搞一下那啥处理掉了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

string s, t;
int k, cnt, dp[100010][2];

signed main() {
  cin >> s >> t >> k;
  s = s + s;
  for(int i = 0; i < s.size() / 2; i++) {
    cnt += s.substr(i, s.size() / 2) == t;
  }
  s = s.substr(0, s.size() / 2);
  dp[0][s != t] = 1;
  for(int i = 1; i <= k; i++) {
    dp[i][0] = ((cnt * dp[i - 1][1]) % 1000000007 + ((cnt - 1) * dp[i - 1][0]) % 1000000007) % 1000000007;
    dp[i][1] = (((s.size() - cnt) * dp[i - 1][0]) % 1000000007 + ((s.size() - cnt - 1) * dp[i - 1][1]) % 1000000007)  % 1000000007;
  }
  cout << dp[k][0] << '\n';
  return 0;
}

B luoguP4824 Censoring S

写一个栈维护当前字符串,然后写一个 hash 维护相等。

或者直接 KMP。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod1 = 998244353, kMod2 = 1000000007, kB = 111111;

struct node {
  char c;
  int h1, h2;
};

int sf1 = 1, sf2 = 1, ht1, ht2;
string s, t;
vector<node> ans;

node suc(node x, char nx) {
  return {nx, (x.h1 * kB + nx) % kMod1, (x.h2 * kB + nx) % kMod2};
}

signed main() {
  cin >> s >> t;
  for(auto i : t) {
    sf1 = sf1 * kB % kMod1;
    sf2 = sf2 * kB % kMod2;
    ht1 = (ht1 * kB + i) % kMod1;
    ht2 = (ht2 * kB + i) % kMod2;
  }
  for(int i = 0; i < s.size(); i++) {
    if(!ans.size()) {
      ans.push_back({s[i], s[i], s[i]});
      continue;
    }
    ans.push_back(suc(ans.back(), s[i]));
    if(ans.size() >= t.size() &&
    (ans.back().h1 - (ans.size() == t.size()? 0 : ans[ans.size() - t.size() - 1].h1 * sf1 % kMod1) + kMod1) % kMod1 == ht1 &&
    (ans.back().h2 - (ans.size() == t.size()? 0 : ans[ans.size() - t.size() - 1].h2 * sf2 % kMod2) + kMod2) % kMod2 == ht2) {
      for(int j = 0; j < t.size(); j++) {
        ans.pop_back();
      }
    }
  }
  for(auto i : ans) {
    cout << i.c;
  }
  cout << '\n';
  return 0;
}

C Codeforces1721E Prefix Function Queries

我们考虑对原串建一个 AC 自动机什么的,然后就支持往末尾添加字符了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

string s, t;
int q, g[1000110][30], p[1000110];

signed main() {
  cin >> s;
  s = ' ' + s;
  g[0][s[1] - 'a'] = 1;
  for(int i = 1; i < s.size(); i++) {
    for(int j = 0; j < 26; j++) {
      if(i < s.size() - 1 && s[i + 1] - 'a' == j) {
        g[i][j] = i + 1;
        p[i + 1] = g[p[i]][j];
      }else {
        g[i][j] = g[p[i]][j];
      }
    }
  }
  for(cin >> q; q--; ) {
    cin >> t;
    for(int j = 0; j < 26; j++) {
      if(t[0] - 'a' == j) {
        g[s.size() - 1][j] = s.size();
        p[s.size()] = g[p[s.size() - 1]][j];
      }
    }
    for(int i = 0; i < t.size(); i++) {
      for(int j = 0; j < 26; j++) {
        if(i < t.size() - 1 && t[i + 1] - 'a' == j) {
          g[i + s.size()][j] = i + s.size() + 1;
          p[i + s.size() + 1] = g[p[i + s.size()]][j];
          //cout << i + s.size() << ' ' << j << ' ' << p[i + s.size()] << ' ' << g[p[i + s.size()]][j] << '\n';
        }else {
          g[i + s.size()][j] = g[p[i + s.size()]][j];
        }
      }
      cout << p[i + s.size()] << ' ';
    }
    for(int j = 0; j < 26; j++) {
      g[s.size() - 1][j] = g[p[s.size() - 1]][j];
    }
    for(int i = 0; i < t.size(); i++) {
      for(int j = 0; j < 26; j++) {
        p[i + s.size()] = g[i + s.size()][j] = 0;
      }
    }
    cout << '\n';
  }
  return 0;
}

D Codeforces898F Restoring the Expression

枚举第三个数的长度。我们发现,要么第一个数的长度与其接近,要么第二个数的长度与其接近(相等或小了 \(1\))。

然后就可以写个双哈希做出本题。注意模数取阴间点。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod1 = 998244353, kMod2 = 631373159;

int n, pw1[1000010], pw2[1000010], c[1000010], pre1[1000010], pre2[1000010], iw1[1000010], iw2[1000010];
string s;

int qinv(int x, int m) {
  int res = 1;
  for(int tmp = m - 2; tmp; tmp >>= 1) {
    if(tmp & 1) {
      res = res * x % m;
    }
    x = x * x % m;
  }
  return res;
}

pair<int, int> get(int l, int r) {
  return {(pre1[r] + kMod1 - pre1[l - 1]) * iw1[r] % kMod1, (pre2[r] + kMod2 - pre2[l - 1]) * iw2[r] % kMod2};
}

void check(int l1, int l2, int l3) {
  if(l1 <= 0 || l2 <= 0 || l3 <= 0) {
    return ;
  }
  if((l1 != 1 && s[1] == '0') || (l2 != 1 && s[l1 + 1] == '0') || (l3 != 1 && s[l1 + l2 + 1] == '0')) {
    return ;
  }
  auto tmp1 = get(1, l1), tmp2 = get(l1 + 1, l1 + l2), tmp3 = get(l1 + l2 + 1, l1 + l2 + l3);
  if((tmp1.first + tmp2.first) % kMod1 == tmp3.first && (tmp1.second + tmp2.second) % kMod2 == tmp3.second) {
    for(int i = 1; i <= l1; i++) {
      cout << s[i];
    }
    cout << '+';
    for(int i = l1 + 1; i <= l1 + l2; i++) {
      cout << s[i];
    }
    cout << '=';
    for(int i = l1 + l2 + 1; i <= l1 + l2 + l3; i++) {
      cout << s[i];
    }
    exit(0);
  }
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> s;
  n = s.size();
  s = ' ' + s;
  pw1[n] = pw2[n] = iw1[n] = iw2[n] = 1;
  int i1 = qinv(10, kMod1), i2 = qinv(10, kMod2);
  for(int i = n - 1; i >= 1; i--) {
    pw1[i] = pw1[i + 1] * 10 % kMod1;
    pw2[i] = pw2[i + 1] * 10 % kMod2;
    iw1[i] = iw1[i + 1] * i1 % kMod1;
    iw2[i] = iw2[i + 1] * i2 % kMod2;
  }
  for(int i = 1; i <= n; i++) {
    pre1[i] = (pre1[i - 1] + pw1[i] * (s[i] - '0')) % kMod1;
    pre2[i] = (pre2[i - 1] + pw2[i] * (s[i] - '0')) % kMod2;
  }
  for(int l3 = 1; l3 <= n; l3++) {
    check(l3 - 1, n - l3 - (l3 - 1), l3);
    check(l3, n - l3 - l3, l3);
    check(n - l3 - (l3 - 1), l3 - 1, l3);
    check(n - l3 - l3, l3, l3);
  }
  return 0;
}

E LuoguP8131 Gene Folding

我们发现,尽可能的进行折叠一定是最优的。读者自证不难。

然后就可以马拉车然后直接折叠了。本题有坑,注意代码实现。

#include <bits/stdc++.h>
using namespace std;

int d[8000080];
string s, t;

void manacher() {
  int l = 0, r = -1;
  for(int i = 0; i < s.size(); i++) {
    int len = (i > r? 1 : min(d[l + r - i], r - i + 1));
    for(; 0 <= i - len && i + len < s.size() && s[i - len] == s[i + len]; len++);
    d[i] = len;
    len--;
    if(i + len > r) {
      r = i + len, l = i - len;
    }
  }
}

int main() {
  cin >> s;
  for(auto i : s) {
    t += '$';
    t += i;
  }
  t += '$';
  s = t;
  manacher();
  int l = 0, r = s.size() - 1;
  for(int i = 0; i < s.size(); i++) {
    if(s[i] == '$' && i - d[i] + 1 <= l) {
      l = i;
    }
    //cout << i << ' ' << s[i] << ' ' << d[i] << ' ' << i - d[i] + 1 << ' ' << l << '\n';
  }
  for(int i = s.size() - 1; i > l; i--) {
    if(s[i] == '$' && i + d[i] - 1 >= r) {
      r = i;
    }
  }
  //cout << l << ' ' << r << '\n';
  cout << (r - l) / 2 << '\n';
  return 0;
}

I Codeforces1202E You are Given Some Strings...

可以发现只要枚举分界点然后统计左边有多少个匹配,右边有多少个匹配即可。

AC 自动机分分钟水过去。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

struct AC {
  int trans[200020][26], cnt = 0, fail[200020], val[200020], acc[200020];
  vector<int> rev, pos;
  
  AC() : trans(), cnt(0), fail(), val(), acc() {
    memset(trans, -1, sizeof(trans));
  }
  
  void insert(string s) {
    int u = 0;
    for(auto i : s) {
      if(trans[u][i - 'a'] == -1) trans[u][i - 'a'] = ++cnt;
      u = trans[u][i - 'a'];
    }
    acc[u]++;
  }
  
  void build() {
    queue<int> q;
    for(int i = 0; i < 26; i++) {
      if(trans[0][i] == -1) {
        trans[0][i] = 0;
      }else {
        q.push(trans[0][i]);
      }
    }
    for(; q.size(); q.pop()) {
      for(int i = 0; i < 26; i++) {
        if(trans[q.front()][i] != -1) {
          fail[trans[q.front()][i]] = trans[fail[q.front()]][i];
          q.push(trans[q.front()][i]);
        }else {
          trans[q.front()][i] = trans[fail[q.front()]][i];
        }
      }
      val[q.front()] += val[fail[q.front()]] + acc[q.front()];
      //cout << q.front() << ' ' << fail[q.front()] << ' ' << trans[q.front()][1] << '\n';
    }
  }
  
  void match(string t) {
    int u = 0;
    for(auto i : t) {
      u = trans[u][i - 'a'];
      //cout << i << ' ' << u << ' ' << val[u] << '\n';
      pos.push_back(val[u]);
    }
  }
}ac, acr;

int n;
string t, s, rt;

signed main() {
  for(cin >> t >> n; n--; ) {
    cin >> s;
    ac.insert(s);
    reverse(s.begin(), s.end());
    acr.insert(s);
  }
  ac.build(), acr.build();
  rt = t;
  reverse(rt.begin(), rt.end());
  ac.match(t), acr.match(rt);
  reverse(acr.pos.begin(), acr.pos.end());
  int ans = 0;
  for(int i = 0; i < t.size() - 1; i++) {
    //cout << ac.pos[i] << ' ' << acr.pos[t.size() - i - 1] << '\n';
    ans += ac.pos[i] * acr.pos[i + 1];
  }
  cout << ans << '\n';
  return 0;
}

Day7A. 模拟赛

秒 A,B 不会,写出一坨屎然后比赛结束才发现挂掉了。100=0。C 的状压 dp 挂大分。

Day7B. dp(1)

按照惯例,每一题都会分 spoiler 并且分 Hint。

A Codeforces1849D Array Painting

Hint 1

如果一个子段能够用 1 个金币搞定,那会有什么性质?

Solution

直接贪心,每一次找到最长的可以用一个金币解决的子段,将其解决即可。

Code
#include <bits/stdc++.h>
using namespace std;

int n, arr[200020];

int main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
  }
  int ans = 0;
  for(int i = 1; i <= n; ) {
    ans++;
    if(arr[i]) {
      int j = i;
      for(; j <= n; j++) {
        if(!arr[j]) {
          j++;
          break;
        }
      }
      i = j;
    }else {
      if(arr[i + 1] == 0) {
        i++;
      }else {
        int j = i + 1, v2 = 0;
        for(; j <= n; j++) {
          if(arr[j] == 0) {
            break;
          }
          v2 |= arr[j] == 2;
        }
        i = j += v2;
      }
    }
    //cout << i << '\n';
  }
  cout << ans << '\n';
  return 0;
}

B Codeforces1458B Glass Half Spilled

Hint 1

固定 \(k\)

Hint 2

考虑选择一些杯子,最优的方案是什么?

Hint 3

我们不想流失更多的水,每一次倒水必定是从一个为选择的位置倒入一个选择过的位置。

Solution

可以发现答案就是 \(\min(\text{选择的杯子的容积之和}, (\text{总共的水量} + \text{选定的杯子的水量之和}) / 2)\)

可以直接设计 \(dp_{i, j, k}\) 表示考虑了 \(i\) 个杯子,选择了 \(j\) 个杯子,容积之和为 \(k\) 的最大水量之和。

滚动数组压缩一下防止 MLE 就可以暴力做了。

Code
#include <bits/stdc++.h>
using namespace std;

int n, dp[110][10010], arr[110], brr[110];

int main() {
  memset(dp, 0x80, sizeof(dp));
  dp[0][0] = 0;
  cin >> n;
  int tb = 0;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i] >> brr[i];
    tb += brr[i];
    for(int j = n - 1; j >= 0; j--) {
      for(int k = 10000 - arr[i]; k >= 0; k--) {
        dp[j + 1][k + arr[i]] = max(dp[j + 1][k + arr[i]], dp[j][k] + brr[i]);
      }
    }
  }
  for(int i = 1; i <= n; i++) {
    double ans = 0;
    for(int j = 0; j <= 10000; j++) {
      ans = max(ans, min(j + 0.0, (dp[i][j] + tb) / 2.00));
    }
    cout << fixed << setprecision(10) << ans << ' ';
  }
  return 0;
}

C Codeforces1077F2 Pictures with Kittens (hard version)

Hint 1

直接设 \(dp_{i, j}\) 为考虑了前 \(i\) 张图片并且转发了 \(i\),总共转发了 \(j\) 张图片时美观度的和的最大值。

Hint 2

拿个那啥维护一下,方便转移。

Solution

其实上面的东西已经讲完了大半个解法,唯一有不同的是这个逼题卡 \(\log\)

Code
#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, k, x, arr[5050], dp[5050][5050];
deque<int> dq[5050];

signed main() {
  cin >> n >> k >> x;
  memset(dp, -1, sizeof(dp));
  for(int i = 0; i <= n + 1; i++) {
    dq[i].push_back(0);
  }
  dp[0][0] = 0;
  for(int i = 1; i <= n + 1; i++) {
    if(i <= n) {
      cin >> arr[i];
    }
    //cout << i << '\n';
    //cout << dp[i][0] << ' ';
    for(int j = 1; j <= n + 1; j++) {
      for(; dq[j - 1].size() && dq[j - 1].front() < i - k; dq[j - 1].pop_front()) {
      }
      if(dp[dq[j - 1].front()][j - 1] != -1) {
        dp[i][j] = dp[dq[j - 1].front()][j - 1] + arr[i];
        //cout << dp[dq[j].front()][j - 1] << ',' << dq[j].front() << ',';
      }
      //cout << dp[i][j] << ' ';
    }
    //cout << '\n';
    for(int j = 0; j <= n + 1; j++) {
      for(; dq[j].size() && dp[dq[j].back()][j] <= dp[i][j]; dq[j].pop_back()) {
      }
      dq[j].push_back(i);
    }
  }
  cout << dp[n + 1][x + 1] << '\n';
  return 0;
}

D Codeforces1874C Jellyfish and EVA

Hint 1

Jellyfish 每一步会怎么选?

Solution

很显然,如果我们不选那个成功率最大的路是一定不优的。所以就可以直接设 \(dp_i\) 为当前在 \(i\) 的最大成功率然后预处理路还存活的概率最后直接 dp 即可。

Code
#include <bits/stdc++.h>
using namespace std;

int t, n, m, a, b;
double dp[5050], val[5050][5050];
vector<int> to[5050];

bool cmp(int x, int y) {
  return dp[x] > dp[y];
}

void precal() {
  val[2][1] = 0.5;
  val[1][1] = 1;
  for(int i = 3; i <= 5000; i++) {
    val[i][1] = 1.0 / i;
    for(int j = 2; j <= i; j++) {
      val[i][j] = (j - 2) * 1.0 / i * val[i - 2][j - 2] + (i - j) * 1.0 / i * val[i - 2][j - 1];
      //cout << (j - 2) * 1.0 / i * val[i - 2][j - 2] << ',' << (i - j) * 1.0 / i * val[i - 2][j - 1] << ',';
      //cout << val[i][j] << ' ';
    }
    //cout << '\n';
  }
}

int main() {
  precal();
  for(cin >> t; t--; ) {
    for(cin >> n >> m; m--; ) {
      cin >> a >> b;
      to[a].push_back(b);
    }
    dp[n] = 1;
    for(int i = n - 1; i >= 1; i--) {
      //cout << i << '\n';
      dp[i] = 0.0;
      sort(to[i].begin(), to[i].end(), cmp);
      for(int j = 0; j < to[i].size(); j++) {
        //cout << i << ' ' << j << ' ' << to[i][j] << ' ' << dp[to[i][j]] << ' ' << val[to[i].size()][j + 1] << '\n';
        dp[i] += dp[to[i][j]] * val[to[i].size()][j + 1];
      }
      //dp[i] = max(dp[i], val[to[i].size()][cnt] * cur);
      //cout << fixed << setprecision(9) << cur << ' ' << cnt << ' ' << val[to[i].size()][cnt] << '\n';
      //cout << i << ' ' << fixed << setprecision(9) << dp[i] << '\n';
    }
    /*
    for(int i = 1; i <= n; i++) {
      cout << i << ": ";
      for(auto j : to[i]) {
        cout << j << ' ';
      }
      cout << '\n';
    }
    //*/
    cout << fixed << setprecision(12) << dp[1] << '\n';
    for(int i = 1; i <= n; i++) {
      to[i].clear();
    }
  }
  return 0;
}
/*
1
9 15
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
2 3
3 4
4 5
5 6
6 7
7 8
8 9
*/

Day 9 dp(2)

A Codeforces1324E Sleeping Shcedule

考虑把已经过去的时间和已经睡的次数作为状态。转移你懂的。

点击查看代码

Element 里面甚至集体选了 A 作为最难的题目

#include <bits/stdc++.h>
using namespace std;

int n,h,l,r,arr[2020],dp[2020][2020],sum,ans;

int main() {
	cin>>n>>h>>l>>r;
	for(int i = 1;i <= n;i++) {
		cin>>arr[i];
		sum+=arr[i];
	}
	fill(dp[0],dp[n+1],INT_MIN);
	dp[0][0]=0;
	for(int i = 1;i <= n;i++) {
		for(int j = 0;j < h;j++) {
			dp[i][j]=max(dp[i-1][(j-arr[i]+h)%h],dp[i-1][(j-arr[i]+1+h)%h])+int(l<=j&&j<=r);
		}
	}
	for(int i = sum - n;i <= sum;i++) {
		ans=max(ans,dp[n][i%h]);
	}
	cout<<ans<<endl;
	return 0;
}

B Codeforces2000F Color Rows and Columns

首先可以预处理出分配了 \(i\) 个格子时最大的得分,然后就是一个类似于背包的东西了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

struct node {
  int cntr, cntc;
}arr[1010];

struct nodee {
  int x, typ, bel;
  
  bool operator <(const nodee &b) const {
    return (x == b.x? (typ == b.typ? bel < b.bel : typ < b.typ) : x < b.x);
  }
};

int t, n, k, val[1010][110], dp[10010];
multiset<nodee> st;

signed main() {
  for(cin >> t; t--; ) {
    cin >> n >> k;
    fill(dp + 1, dp + k + 1, 100000000);
    for(int i = 1; i <= n; i++) {
      cin >> arr[i].cntr >> arr[i].cntc;
      for(int j = 1; j <= arr[i].cntr; j++) {
        st.insert({arr[i].cntc, 0, i});
      }
      for(int j = 1; j <= arr[i].cntc; j++) {
        st.insert({arr[i].cntr, 1, i});
      }
      int cnt = 0, res = 0;
      for(; ; ) {
        if(!st.size()) {
          break;
        }
        nodee tmp = *st.begin();
        st.erase(st.begin());
        res += tmp.x;
        cnt++;
        val[i][cnt] = res;
        if(tmp.typ == 0) {
          for(int j = 1; j <= arr[tmp.bel].cntc; j++) {
            st.erase(st.find({arr[tmp.bel].cntr, 1, tmp.bel}));
          }
          arr[tmp.bel].cntr--;
          for(int j = 1; j <= arr[tmp.bel].cntc; j++) {
            st.insert({arr[tmp.bel].cntr, 1, tmp.bel});
          }
        }else {
          for(int j = 1; j <= arr[tmp.bel].cntr; j++) {
            st.erase(st.find({arr[tmp.bel].cntc, 0, tmp.bel}));
          }
          arr[tmp.bel].cntc--;
          for(int j = 1; j <= arr[tmp.bel].cntr; j++) {
            st.insert({arr[tmp.bel].cntc, 0, tmp.bel});
          }
        }
      }
      //cout << cnt << '\n';
      for(int j = k; j >= 1; j--) {
        for(int x = 1; x <= min(cnt, j); x++) {
          dp[j] = min(dp[j], dp[j - x] + val[i][x]);
        }
      }
    }
    //cout << '\n';
    cout << (dp[k] == 100000000? -1 : dp[k]) << '\n';
  }
  return 0;
}

C Codeforces Ezzat and Grid

考虑 \(dp_{i, j}\) 表示考虑了 \(i\) 个行,并且最后一行的第 \(j\) 列有 \(1\) 的方案数。

转移易知,考虑优化。我们写一棵线段树,然后在线段树上维护即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;

struct node {
  int x, l, r;
}arr[300030];

struct dat {
  int dd, pre;
  
  dat operator ^(const dat &b) const {
    return {max(dd, b.dd), dd >= b.dd? pre : b.pre};
  }
}d[2400024];

int n, m, nn, tmp[600060], t[2400024], pre[300030];
set<int> st;

void pushdn(int id) {
  if(t[id] == -1) return ;
  if(d[id * 2].dd < t[id]) d[id * 2].pre = d[id].pre;
  if(d[id * 2 + 1].dd < t[id]) d[id * 2 + 1].pre = d[id].pre;
  d[id * 2].dd = d[id * 2 + 1].dd = t[id];
  t[id * 2] = t[id * 2 + 1] = t[id];
  t[id] = -1;
}

void build(int id = 1, int l = 1, int r = nn) {
  t[id] = -1;
  if(l == r) {
    return ;
  }
  int mid = (l + r) >> 1;
  build(id * 2, l, mid);
  build(id * 2 + 1, mid + 1, r);
}

void modify(int ml, int mr, int v, int x, int id = 1, int l = 1, int r = nn) {
  //cout << ml << ' ' << mr << ' ' << v << ' ' << x << ' ' << id << ' ' << l << ' ' << r << '\n';
  if(r < ml || mr < l) {
    return ;
  }
  if(ml <= l && r <= mr) {
    //cout << d[id].dd << ' ' << d[id].pre << '\n';
    if(d[id].dd < v) d[id].pre = x;
    d[id].dd = t[id] = v;
    return ;
  }
  pushdn(id);
  int mid = (l + r) >> 1;
  modify(ml, mr, v, x, id * 2, l, mid);
  modify(ml, mr, v, x, id * 2 + 1, mid + 1, r);
  d[id] = d[id * 2] ^ d[id * 2 + 1];
  //cout << 'm' << ' ' << d[id].dd << ' ' << d[id].pre << '\n';
}

dat query(int ql, int qr, int id = 1, int l = 1, int r = nn) {
  if(r < ql || qr < l) {
    return {0, 0};
  }
  if(ql <= l && r <= qr) {
    return d[id];
  }
  pushdn(id);
  int mid = (l + r) >> 1;
  return query(ql, qr, id * 2, l, mid) ^ query(ql, qr, id * 2 + 1, mid + 1, r);
}

int main() {
  cin >> n >> m;
  for(int i = 1; i <= n; i++) st.insert(i);
  for(int i = 1; i <= m; i++) {
    cin >> arr[i].x >> arr[i].l >> arr[i].r;
    tmp[++nn] = arr[i].l, tmp[++nn] = arr[i].r;
  }
  sort(arr + 1, arr + m + 1, [](node a, node b) {return a.x == b.x? (a.l == b.l? a.r < b.r : a.l < b.l) : a.x < b.x;});
  sort(tmp + 1, tmp + nn + 1);
  nn = unique(tmp + 1, tmp + nn + 1) - tmp - 1;
  for(int i = 1; i <= m; i++) {
    arr[i].l = lower_bound(tmp + 1, tmp + nn + 1, arr[i].l) - tmp;
    arr[i].r = lower_bound(tmp + 1, tmp + nn + 1, arr[i].r) - tmp;
  }
  for(int i = 1, j = 1; i <= n; i++) {
    int cur = j;
    dat res = {0, 0};
    for(; arr[cur].x == i; cur++) {
      res = res ^ query(arr[cur].l, arr[cur].r);
    }
    res.dd++, pre[i] = res.pre;
    for(cur = j; arr[cur].x == i; cur++) {
      //cout << cur << ' ' << arr[cur].l << ' ' << arr[cur].r << ' ' << res.dd << ' ' << i << '\n';
      modify(arr[cur].l, arr[cur].r, res.dd, i);
    }
    j = cur;
  }
  for(int i = query(1, nn).pre; i; i = pre[i]) {
    st.erase(i);
  }
  cout << n - query(1, nn).dd << '\n';
  for(auto i : st) cout << i << ' ';
  cout << '\n';
  return 0;
}

Day10A. 模拟赛

开局秒了 A,然后发布一个 clarification。

得到回答后发现 B 读假了。搞毛。写完了 B,C 只会 9 分,走人了 /kk

Day11. 组合数学(1)

无脑题,随便写一写就过了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = (int)1e9 + 7;

int n, x, pos, gc, lc, fac[1010];

void lrmid() {
  int l = 0, r = n, res = 1;
  for(; l < r; ) {
    int mid = (l + r) >> 1;
    if(pos < mid) {
      res = res * gc % kMod;
      gc--;
      r = mid;
    }else {
      if(pos > mid) {
        res = res * lc % kMod;
        lc--;
      }
      l = mid + 1;
    }
  }
  cout << res * fac[gc + lc] % kMod << '\n';
}

signed main() {
  cin >> n >> x >> pos;
  gc = n - x, lc = x - 1;
  fac[0] = 1;
  for(int i = 1; i <= 1000; i++) {
    fac[i] = fac[i - 1] * i % kMod;
  }
  lrmid();
  return 0;
}

B LuoguP9306 进行一个排的重(Minimum Version)

可以发现当两个 \(n\) 在同一个位置时第一问的答案为 \(2\),否则为 \(3\)

然后:

  • 当答案为 \(2\) 时,容易发现,剩下的元素可以随意排列,所以方案数为 \((n - 1)!\)

  • 否则,考虑把 \(p_i = n\)\(i\) 放在第一个,可以发现这时在放入 \(q_j = n\)\(j\) 之前只能放入 \(q_j < q_i\)\(j\),所以方案数为 \(\dfrac{(n - 1)!}{n - q_i}\)。另一种情况是等价的,所以这时的总方案书为 \(\dfrac{(n - 1)!}{n - q_i} + \dfrac{(n - 1)!}{n - p_j}\)

然后搞个那啥写一下就可以了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

int n, p[500050], q[500050], res;

int qinv(int x) {
  int res = 1;
  for(int tmp = kMod - 2; tmp; tmp >>= 1) {
    if(tmp & 1) {
      res = res * x % kMod;
    }
    x = x * x % kMod;
  }
  return res;
}

signed main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> p[i];
  }
  for(int i = 1; i <= n; i++) {
    cin >> q[i];
  }
  int x = 0, y = 0;
  for(int i = 1; i <= n; i++) {
    if(p[i] == n) x = i;
    if(q[i] == n) y = i;
  }
  int res = 1;
  for(int i = 1; i < n; i++) {
    res = res * i % kMod;
  }
  if(x == y) {
    cout << 2 << ' ' << res << '\n';
  }else {
    cout << 3 << ' ' << res * (qinv(n - q[x]) + qinv(n - p[y])) % kMod << '\n';
  }
  return 0;
}

C Codeforces1696E Placing Jinas

可以发现在 \((i, j)\) 所需的操作是 \(\binom{i + j}{j}\),所以答案为 \(\sum \limits_{i = 0}^n \sum \limits_{j = 0}^{a_i} \binom{i + j}{j} = \sum \limits_{i = 0}^n \binom{i + a_i}{i + 1}\)

随便写写就可以了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = (int)1e9 + 7;

int n, arr[200020], fac[400040], inv[400040];

int qinv(int x) {
  int res = 1;
  for(int tmp = kMod - 2; tmp; tmp >>= 1) {
    if(tmp & 1) {
      res = res * x % kMod;
    }
    x = x * x % kMod;
  }
  return res;
}

void precal() {
  fac[0] = 1;
  for(int i = 1; i <= 400000; i++) {
    fac[i] = fac[i - 1] * i % kMod;
  }
  inv[400000] = qinv(fac[400000]);
  for(int i = 399999; i >= 0; i--) {
    inv[i] = inv[i + 1] * (i + 1) % kMod;
  }
}

int c(int x, int y) {
  if(x < 0 || y < 0 || x < y) return 0;
  return fac[x] * inv[y] % kMod * inv[x - y] % kMod;
}

signed main() {
  precal();
  cin >> n;
  int res = 0;
  for(int i = 0; i <= n; i++) {
    cin >> arr[i];
    res = (res + c(i + arr[i], i + 1)) % kMod;
  }
  cout << res << '\n';
  return 0;
}

E Codeforces1929F Sasha and the Wedding Binary Search Tree

考虑吧这个 BST 干成一个序列,然后本题无思维难度。注意写组合数时需要暴力乘。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

int t, n, c, lc[500050], rc[500050], val[500050], arr[500050], cnt;

void med(int x) {
  if(x == -1) return ;
  med(lc[x]);
  arr[++cnt] = val[x];
  med(rc[x]);
}

int qinv(int x) {
  int res = 1;
  for(int tmp = kMod - 2; tmp; tmp >>= 1) {
    if(tmp & 1) {
      res = res * x % kMod;
    }
    x = x * x % kMod;
  }
  return res;
}

int C(int x, int y) {
  if(x < 0 || y < 0 || x < y) return 0;
  if(y == 0) return 1;
  return C(x - 1, y - 1) * x % kMod * qinv(y) % kMod;
}

signed main() {
  for(cin >> t; t--; ) {
    cin >> n >> c;
    for(int i = 1; i <= n; i++) {
      cin >> lc[i] >> rc[i] >> val[i];
    }
    cnt = 0;
    med(1);
    arr[0] = 1, arr[cnt + 1] = c;
    int lst = 0, len = 0, ans = 1;
    for(int i = 1; i <= n + 1; i++) {
      if(arr[i] == -1) {
        if(!lst) lst = i;
        len++;
      }else {
        //cout << i << ' ' << lst << ' ' << arr[i] << ' ' << arr[lst - 1] << ' ' << len << '\n';
        if(len) ans = ans * C((arr[i]) - (arr[lst - 1]) + len, len) % kMod;
        len = lst = 0;
      }
    }
    cout << ans << '\n';
  }
  return 0;
}

F Codeforces1227F2 Wrong Answer on test 233 (Hard Version)

考虑所有可以为答案做出贡献的点。枚举不会做出任何贡献的点,然后你会得到一坨 shit。不难发现这一坨 shit 可以被简化成另一个式子,\(O(n \log V)\) 计算即可。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

int n, k, h[200020], fac[200020], inv[200020];

int qpow(int x, int y) {
  int res = 1;
  for(; y; y >>= 1) {
    if(y & 1) {
      res = res * x % kMod;
    }
    x = x * x % kMod;
  }
  return res;
}

int qinv(int x) {
  return qpow(x, kMod - 2);
}

void precal() {
  fac[0] = 1;
  for(int i = 1; i <= 200000; i++) {
    fac[i] = fac[i - 1] * i % kMod;
  }
  inv[200000] = qinv(fac[200000]);
  for(int i = 199999; i >= 0; i--) {
    inv[i] = inv[i + 1] * (i + 1) % kMod;
  }
}

int c(int x, int y) {
  if(x < 0 || y < 0 || x < y) return 0;
  return fac[x] * inv[y] % kMod * inv[x - y] % kMod;
}

signed main() {
  precal();
  cin >> n >> k;
  for(int i = 1; i <= n; i++) {
    cin >> h[i];
  }
  if(k == 1) {
    cout << 0 << '\n';
    return 0;
  }
  int x = 0, res = 0;
  for(int i = 1; i <= n; i++) {
    if(h[i] != h[i % n + 1]) x++;
  }
  for(int i = 0; i < x; i++) {
    int fm = qpow(k - 2, i) * c(x, i) % kMod;
    //cout << qpow(k - 2, i) << ' ' << c(x, i) << '\n';
    if((x - i) % 2) {
      fm = fm * qpow(2, x - i - 1) % kMod;
    }else {
      fm = fm * (qpow(2, x - i) + kMod - c(x - i, (x - i) / 2)) % kMod * qinv(2) % kMod;
    }
    res = (res + fm) % kMod;
  }
  cout << res * qpow(k, n - x) % kMod << '\n';
  return 0;
}

G LuoguP7118 Galgame

先处理掉节点个数更小的情况。这是 Catalan 数前缀和。

然后考虑节点个数相同的情况。首先递归处理掉左边点数相同但左边更小或左边完全相同的右边更小的情况,这是一个子问题。

然后考虑左边节点更少的情况。枚举左边的节点数量,答案易得。

但这样容易爆炸。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

int n, arr[1000010], brr[1000010], catalan[1000010], sz[1000010];

int qinv(int x) {
  int res = 1;
  for(int tmp = kMod - 2; tmp; tmp >>= 1) {
    if(tmp & 1) {
      res = res * x % kMod;
    }
    x = x * x % kMod;
  }
  return res;
}

void precal() {
  catalan[0] = 1;
  for(int i = 1; i <= 1000000; i++) {
    catalan[i] = catalan[i - 1] * (4 * i - 2) % kMod * qinv(i + 1) % kMod;
  }
}

int gans(int x) {
  if(!x) return 0;
  sz[x] = 1;
  int res = gans(brr[x]);
  sz[x] += sz[brr[x]];
  res = (res + gans(arr[x]) * catalan[sz[brr[x]]]) % kMod;
  sz[x] += sz[arr[x]];
  int tmp = 0;
  if(sz[arr[x]] <= sz[brr[x]]) {
    for(int j = 1; j <= sz[arr[x]]; j++) {
      tmp = (tmp + catalan[sz[arr[x]] - j] * catalan[sz[brr[x]] + j]) % kMod;
    }
  }else {
    for(int j = 0; j <= sz[brr[x]]; j++) {
      tmp = (tmp + catalan[sz[brr[x]] - j] * catalan[sz[arr[x]] + j]) % kMod;
    }
    tmp = (catalan[sz[x]] + kMod - tmp) % kMod;
  }
  //cout << x << ' ' << res << ' ' << tmp << '\n';
  return (res + tmp) % kMod;
}

signed main() {
  precal();
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i] >> brr[i];
  }
  int ans = 0;
  for(int i = 1; i < n; i++) {
    ans = (ans + catalan[i]) % kMod;
  }
  //cout << ans << '\n';
  cout << (ans + gans(1)) % kMod << '\n';
  return 0;
}

Day13A. 模拟赛

A>B。

秒了 B,死活不会 A,炸了 |:(

Day13B. 组合数学(2)

A Codeforces1795D Triangle Coloring

可以发现对于每一个三角形,我们一定会舍弃最小的权值。

所以方案数就是 \(\dbinom{\frac{n}{3}}{\frac{n}{6}}\),乘上在每一个三元环内选最小边的方案数。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

int n, arr[300030];

int qinv(int x) {
  int res = 1;
  for(int tmp = kMod - 2; tmp; tmp >>= 1) {
    if(tmp & 1) res = res * x % kMod;
    x = x * x % kMod;
  }
  return res;
}

int c(int x, int y) {
  if(x < 0 || y < 0 || x < y) return 0;
  if(y == 0) return 1;
  return c(x - 1, y - 1) * x % kMod * qinv(y) % kMod;
}

signed main() {
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i];
  }
  int res = 1;
  for(int i = 1; i <= n; i += 3) {
    int a = min({arr[i], arr[i + 1], arr[i + 2]});
    res = res * ((arr[i] == a) + (arr[i + 1] == a) + (arr[i + 2] == a)) % kMod;
  }
  cout << res * c(n / 3, n / 6) % kMod << '\n';
  return 0;
}

B Codeforces1207D Number Of Permutations

考虑容斥,只有一个序列有序的方案是易得的,而两个序列均有序的方案可以先排个序检查是否有方案然后同理。然后就可以快乐的容斥了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int kMod = 998244353;

struct node {
  int a, b;
  
  bool operator <(const node &b) const {
    return a == b.a? (*this).b < b.b : a < b.a;
  }
}arr[300030];

int n, cnt1[300030], cnt2[300030], fac[300030];
map<node, int> cnt3;

signed main() {
  cin >> n, fac[0] = 1;
  for(int i = 1; i <= n; i++) {
    cin >> arr[i].a >> arr[i].b;
    cnt1[arr[i].a]++, cnt2[arr[i].b]++, cnt3[arr[i]]++;
    fac[i] = fac[i - 1] * i % kMod;
  }
  sort(arr + 1, arr + n + 1);
  int ca = 1, cb = 1, cc = 1;
  for(int i = 1; i <= n; i++) {
    ca = ca * fac[cnt1[i]] % kMod;
    cb = cb * fac[cnt2[i]] % kMod;
  }
  for(auto i : cnt3) {
    cc = cc * fac[i.second] % kMod;
  }
  for(int i = 1; i < n; i++) {
    if(arr[i].a > arr[i + 1].a || arr[i].b > arr[i + 1].b) {
      cc = 0;
      break;
    }
  }
  cout << (fac[n] + cc - ca - cb + kMod + kMod) % kMod << '\n';
  return 0;
}

F Codeforces1924D Balanced Subsequences

Day14 图论(1)

posted @ 2025-07-21 19:43  hhc0001  阅读(58)  评论(0)    收藏  举报