题解:QOJ10905 [Shanghai21F] Kaiji

题解:QOJ10905 [Shanghai21F] Kaiji

题目描述

Alice 有 \(n\) 个外形完全相同的球,第 \(i\) 个球里写了数字 \(a_i\)

Alice 和 Bob 玩游戏。

Alice 选出两个球 \(i,j\),满足不存在 \(k\) 使得 \((a_i-a_k)(a_j-a_k)<0\) (即不存在数字严格夹在中间的球),然后把它们分别放在 Bob 的左右手。

Bob 的眼睛被蒙住了,他并不知道 Alice 选了哪两个球。

Bob 需要选择“左手”或“右手”。如果选择了左手,那么 Alice 会告诉 Bob \(a_i\) ,然后 Bob 需要猜测 \(a_i,a_j\) 的大小关系是 \(>,=,<\) 的哪一种。

同理如果选择了右手则 Alice 会告诉 Bob \(a_j\) ,Bob 仍需猜测大小关系。若 Bob 猜对了则 Bob 获胜,否则 Alice 获胜。

帮 Bob 选择一个最优的随机策略,求出 Bob 能获得的最大胜率 \(p\) ,使得无论 Alice 选的是哪两个球,胜率都至少是 \(p\)  。答案模 \(998244353\) 输出。

题解

首先左右手是没有区别的,只能随机选,但是选完之后猜大小可以根据得到的数字来按照预设的一个概率猜测,即我们设 \(f_i,g_i,h_i\) 表示抽到 \(i\) 数字后以这么多的概率猜测小于、等于、大于,这样就有可能提高我们的存活率。

设答案为 \(ans\),则我们考虑我们手上实际拿的数字,假如是 \(x\leq y\)。如果 \(x\neq y\),则有 \(\frac 1 2\) 的概率选中 \(x\),然后以 \(f_i\) 的概率活下来;有 \(\frac 1 2\) 的概率选中 \(y\),然后以 \(h_y\) 的概率活下来。两个条件告诉我们 \(\frac{f_x+h_y}{2}\geq ans\)。如果 \(x=y\),我们有 \(g_x\) 的概率猜等于,这需要 \(g_x\geq ans\)

整理一下,删去所有没有出现过的数字进行离散化后,需要 \(g_i\geq ans\cdot [cnt_i\geq 2],f_{i}+h_{i+1}\geq 2ans\)。设 \(t_i=[cnt_i\geq 2]\)。不等式还是太难,我们贪心一下,使得 \(g_i=ans\cdot t_i\),剩下的部分都分给 \(f_i,h_i\),这样就有:\(h_i=1-f_i-ans\cdot t_i\),可以推出

\[f_i+1-f_{i+1}-ans\cdot t_{i+1}\geq 2ans \]

同时由 \(g_i\) 的限制:

\[0\leq f_i\leq 1-ans\cdot t_i \]

也就是说

\[0\leq f_i=\min(f_{i-1}+1-2ans,1)-ans\cdot t_i \]

(注意 \(f_1=1-ans\cdot t_i\) 因为 \(h_1=0\))这个就有一点唐,因为 \(ans\) 才是我们要求的东西,这样的话我们可以二分,复杂度 \(O(-n\log \epsilon)\)。但是本题答案要取模,这对精度和时间要求有一点高。

可以发现 \(f_i=a-b\times ans\),以 \(ans\) 为横轴,\(f_i\) 为纵轴,建立平面直角坐标系,维护 \(f_i\) 关于 \(ans\) 变化的一次折线函数。这个函数是单调递减的。维护每一段折线,然后每次进行全局的 \(a\)\(b\) 加,并从头开始将 \(>1\) 部分的折线弹掉再加新的,从尾开始彻底删掉 \(<0\) 的部分,就能维护了。

\(ans\) 是一个分数,需要判断 \(a-b\times ans\)\(0\) 的关系,但是你移一下项就会发现这个分数其实很好比较,不需要所有分数运算都写出来,更不需要约分和 __int128

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

代码

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 10000010;
template <unsigned umod>
struct modint {/*{{{*/
  static constexpr int mod = umod;
  unsigned v;
  modint() = default;
  template <class T, enable_if_t<is_integral<T>::value, int> = 0>
    modint(const T& y) : v((unsigned)(y % mod + (is_signed<T>() && y < 0 ? mod : 0))) {}
  modint& operator+=(const modint& rhs) { v += rhs.v; if (v >= umod) v -= umod; return *this; }
  modint& operator-=(const modint& rhs) { v -= rhs.v; if (v >= umod) v += umod; return *this; }
  modint& operator*=(const modint& rhs) { v = (unsigned)(1ull * v * rhs.v % umod); return *this; }
  modint& operator/=(const modint& rhs) { assert(rhs.v); return *this *= qpow(rhs, mod - 2); }
  friend modint operator+(modint lhs, const modint& rhs) { return lhs += rhs; }
  friend modint operator-(modint lhs, const modint& rhs) { return lhs -= rhs; }
  friend modint operator*(modint lhs, const modint& rhs) { return lhs *= rhs; }
  friend modint operator/(modint lhs, const modint& rhs) { return lhs /= rhs; }
  template <class T> friend modint qpow(modint a, T b) {
    modint r = 1;
    for (assert(b >= 0); b; b >>= 1, a *= a) if (b & 1) r *= a;
    return r;
  }
  friend int raw(const modint& self) { return self.v; }
  friend ostream& operator<<(ostream& os, const modint& self) { return os << raw(self); }
  explicit operator bool() const { return v != 0; }
  modint operator-() const { return modint(0) - *this; }
  bool operator==(const modint& rhs) const { return v == rhs.v; }
  bool operator!=(const modint& rhs) const { return v != rhs.v; }
};/*}}}*/
using mint = modint<998244353>;
int n, A, B, C, M, a0;
uint8_t cnt[N];
struct frac {
  int x, y;
};
struct node {
  frac lpos;
  int a, b;
} q[N];
auto calc(int a, int b, frac v) {
  return 1ll * v.y * a - 1ll * v.x * b;
}
int mian() {
  cin >> n >> a0 >> A >> B >> C >> M;
  for (int i = 1; i <= n; i++) cnt[i] = 0;
  for (int i = 1; i <= n; i++) {
    a0 = (1ll * A * a0 % M * a0 + 1ll * B * a0 + C) % M + 1;
    if (cnt[a0]) cnt[a0] = 2; else cnt[a0] = 1;
  }
  int l = n, r = n - 1;
  frac rlim = {1, 1};
  int ta = 0, tb = 0;
  for (int i = 1; i <= n; i++) if (cnt[i]) {
//  debug("i = %d\n", i);
    int t = cnt[i] > 1 ? 1 : 0;
    if (l > r) {
      q[++r] = {{0, 1}, 1 - ta, t - tb};
      continue;
    }
    ta += 1, tb += 2;
    frac llim = q[l].lpos;
    while (l <= r && calc(ta + q[l].a - 1, tb + q[l].b, q[l].lpos) > 0) {
      auto nxt = l == r ? rlim : q[l + 1].lpos;
      if (calc(ta + q[l].a - 1, tb + q[l].b, nxt) > 0) l++;
      else {
        q[l].lpos = {ta + q[l].a - 1, tb + q[l].b};
        break;
      }
    }
    q[--l] = {llim, 1 - ta, 0 - tb};
    tb += t;
    while (l <= r && calc(ta + q[r].a, tb + q[r].b, rlim) < 0) {
      if (calc(ta + q[r].a, tb + q[r].b, q[r].lpos) < 0) rlim = q[r--].lpos;
      else rlim = {ta + q[r].a, tb + q[r].b};
    }
  }
//debug("ans = %d / %d\n", rlim.x, rlim.y);
  cout << mint(rlim.x) / rlim.y << endl;
  return 0;
}
int main() {
#ifndef LOCAL
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  int t;
  cin >> t;
  while (t--) mian();
  return 0;
}
posted @ 2025-05-26 20:19  caijianhong  阅读(59)  评论(0)    收藏  举报