[AGC061C] First Come First Serve 题解

Description

\(n\) 个人来过,第 \(i\) 个人在 \(a_i\) 时刻来在 \(b_i\) 时刻走,每个人可以在来时或走时登记,问可能的登记顺序有多少种。

\(n\leq 5\times 10^5\)\(a_i,b_i\) 互不相同,\(\forall i<n,a_i<a_{i+1},b_{i}<b_{i+1}\)

Solution

首先如果每个人随便选,有 \(2^n\) 种方案。但这显然会算重。考虑构造一种方案使得选的方案和最终排列一一对应。

按照排列从小到大的顺序,如果当前 \((a_i,b_i)\) 中有数,则选 \(a_i\),否则选 \(b_i\),容易发现这样做一定能够一一对应。

这时一个选择不合法当且仅当对于某个 \(i\)\((a_i,b_i)\) 中没数但 \(i\) 选了 \(b_i\),可以对不合法的位置数进行容斥。

具体的,如果选了 \(a_i\)\(b_i\),则系数为 \(1\)\((a_i,b_i)\) 中没数且选 \(b_i\),则系数为 \(-1\)

容易发现对于任意两个钦定了不合法的位置 \(i,j\)\([a_i,b_i]\)\([a_j,b_j]\) 一定不相交。并且如果设 \(pre_i\) 表示最大的 \(b_j<a_i\)\(j\)\(nxt_i\) 表示最小的 \(a_j>b_i\)\(j\),那么如果 \(i\) 不合法,\((pre_i,i)\)\((i,nxt_i)\) 选择一定是固定的。这样就可以 dp 了。

\(f_i\) 表示前 \(i\) 个数的系数之和。那么 \(f_i=2f_{i-1}-\sum_{nxt_j+1=i}{f_{pre_j}}\)

时间复杂度:\(O(n)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e5 + 5, kMod = 998244353;

int n;
int a[kMaxN], b[kMaxN], pre[kMaxN], nxt[kMaxN], f[kMaxN];
std::vector<int> vec[kMaxN];

constexpr int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) std::cin >> a[i] >> b[i];
  for (int i = 1, j = 0; i <= n; ++i) {
    for (; b[j] < a[i]; ++j) {}
    pre[i] = j;
  }
  a[n + 1] = b[n + 1] = 2 * n + 1;
  for (int i = n, j = n + 1; i; --i) {
    for (; a[j] > b[i]; --j) {}
    nxt[i] = j;
  }
  for (int i = 1; i <= n; ++i) vec[nxt[i]].emplace_back(pre[i] - 1);
  f[0] = 1;
  for (int i = 1; i <= n; ++i) {
    f[i] = 2ll * f[i - 1] % kMod;
    for (auto j : vec[i]) dec(f[i], f[j]);
  }
  std::cout << f[n] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2024-10-08 10:26  下蛋爷  阅读(4)  评论(0编辑  收藏  举报