Removing People 题解
前言
题意简述
\(n\) 人站成一个圆圈,按顺时针方向依次为 \(1, 2, \cdots, n\)。
每个人面对的方向由长度为 \(n\) 的字符串 \(S\) 给出。对于第 \(i\) 个人,如果 \(S_i = \texttt{L}\),则 \(i\) 面向逆时针方向。如果 \(S_i = \texttt{R}\),则面向顺时针方向。
重复 \(n-1\) 次以下操作:以相等的概率从剩余的人中选择一个,并从圆中移除离被选中的人最近的人。这样做的代价等于被选中的人到被移除的人的距离。
定义从 \(i\) 到 \(j\)(\(i \neq j\))的距离 \(\operatorname{dis}(i, j)\) 为,\(i\) 按照其方向行走多少步能够到达 \(j\)。
求 \(n-1\) 次操作后代价之和的期望值,对 \(M = 998244353\) 取模。
\(2 \leq n \leq 300\)。
题目分析
期望类题目,我们学过的算法好像只有 DP 吧?考虑 DP。状态如何设计呢?移除一个人,难道我们要把 \(n\) 个人还在不在压到状态里吗?显然不行。正难则反,我们考虑从只有 \(1\) 个人开始,逐渐往里面加入一个人。
加入 \(i\),在反转操作前是删除 \(i\),说明我们选择了一个 \(j\),且 \(i\) 是 \(j\) 朝着其方向前进遇到的第一个人,将 \(j\) 到 \(i\) 的距离累加到答案中去。
我们注意到,对于 \(j\) 到 \(i\) 中间的 \(k\),如果加入 \(k\),只可能是选择了 \(i\) 或 \(j\) 其中的一个。加入 \(k\) 之后,分成了两个区间,就又形成了规模更小的子问题,且问题仅和区间两端有关!
考虑区间 DP。设 \(f_{l, r}\) 表示 \(l\) 到 \(r\) 中,最初仅有 \(l\) 和 \(r\) 加入了,经过了若干次操作,把中间的所有人都加入的期望价值。可是,如果要算期望,我们需要知道目前已经放下了多少个人,不然算不了概率,而这是我们状态之外的东西。
那就别记期望了吧,把期望变成价值和比上方案数。方案数显然是 \(n!\),那么我们 DP 价值和,即记 \(f_{l, r}\) 表示把 \(l\) 到 \(r\) 中间的放下的价值和,为了转移需要再记一个方案数 \(g_{l, r}\)。
先来考虑 \(g\) 的转移。先枚举 \(k\) 表示第一个放下的,这里需要注意,我们是选择 \(l\) 或 \(r\) 的哪一个,导致 \(k\) 被放下的呢?如果合法,都有可能,所以这里的方案数为 \([S_l = \texttt{R}] + [S_r = \texttt{L}]\)。放下后,两个子问题的方案数直接相乘 \(g_{l, k} g_{k, r}\) 就行了吗?并不是,因为我们可以交叉着放置,即先放置左边区间的某一个,再放置右边的某一个,以此类推。这一部分的方案数是合并两个有序序列的方案数,设 \(x = \operatorname{dis}(l, k) - 1, y = \operatorname{dis}(k, r) - 1\),即合并两个长度分别为 \(x, y\) 的有序序列,考虑最终长度为 \(x + y\) 的序列中选出 \(x\) 个位置作为其中一个有序序列,方案数是 \(\dbinom{x + y}{x}\)。
说了这么多,其实就是一个转移方程:
\(f\) 的转移很类似,如果是选择 \(l\) 导致 \(k\) 被加入:
选择 \(r\) 同理有:
注意到,之所以我一直避免 \(j - i\) 之类的出现,是因为这是一个环,读者要处理好环的问题。
DP 初值考虑相邻的两项的 \(g = 1\)。答案即为 \(\dfrac{\sum f_{i, i + n}}{n!}\)。
时间复杂度:\(\Theta(n ^ 3)\)。
代码
展开取模板子
namespace Mod_Int_Class {
template <typename T, typename _Tp>
constexpr bool in_range(_Tp val) {
return std::numeric_limits<T>::min() <= val && val <= std::numeric_limits<T>::max();
}
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
static constexpr inline bool is_prime(_Tp val) {
if (val < 2) return false;
for (_Tp i = 2; i * i <= val; ++i)
if (val % i == 0)
return false;
return true;
}
template <auto _mod = 998244353, typename T = int, typename S = long long>
class Mod_Int {
static_assert(in_range<T>(_mod), "mod must in the range of type T.");
static_assert(std::is_integral<T>::value, "type T must be an integer.");
static_assert(std::is_integral<S>::value, "type S must be an integer.");
public:
constexpr Mod_Int() noexcept = default;
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
constexpr Mod_Int(_Tp v) noexcept: val(0) {
if (0 <= T(v) && T(v) < mod) val = v;
else val = (T(v) % mod + mod) % mod;
}
constexpr T const& raw() const {
return this -> val;
}
static constexpr T mod = _mod;
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
constexpr friend Mod_Int pow(Mod_Int a, _Tp p) {
return a ^ p;
}
constexpr friend Mod_Int sub(Mod_Int a, Mod_Int b) {
return a - b;
}
constexpr friend Mod_Int& tosub(Mod_Int& a, Mod_Int b) {
return a -= b;
}
constexpr friend Mod_Int add(Mod_Int a) { return a; }
template <typename... args_t>
constexpr friend Mod_Int add(Mod_Int a, args_t... args) {
return a + add(args...);
}
constexpr friend Mod_Int mul(Mod_Int a) { return a; }
template <typename... args_t>
constexpr friend Mod_Int mul(Mod_Int a, args_t... args) {
return a * mul(args...);
}
template <typename... args_t>
constexpr friend Mod_Int& toadd(Mod_Int& a, args_t... b) {
return a = add(a, b...);
}
template <typename... args_t>
constexpr friend Mod_Int& tomul(Mod_Int& a, args_t... b) {
return a = mul(a, b...);
}
template <T __mod = mod, typename = std::enable_if_t<is_prime(__mod)>>
static constexpr inline T inv(T a) {
assert(a != 0);
return _pow(a, mod - 2);
}
constexpr Mod_Int& operator + () const {
return *this;
}
constexpr Mod_Int operator - () const {
return _sub(0, val);
}
constexpr Mod_Int inv() const {
return inv(val);
}
constexpr friend inline Mod_Int operator + (Mod_Int a, Mod_Int b) {
return _add(a.val, b.val);
}
constexpr friend inline Mod_Int operator - (Mod_Int a, Mod_Int b) {
return _sub(a.val, b.val);
}
constexpr friend inline Mod_Int operator * (Mod_Int a, Mod_Int b) {
return _mul(a.val, b.val);
}
constexpr friend inline Mod_Int operator / (Mod_Int a, Mod_Int b) {
return _mul(a.val, inv(b.val));
}
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
constexpr friend inline Mod_Int operator ^ (Mod_Int a, _Tp p) {
return _pow(a.val, p);
}
constexpr friend inline Mod_Int& operator += (Mod_Int& a, Mod_Int b) {
return a = _add(a.val, b.val);
}
constexpr friend inline Mod_Int& operator -= (Mod_Int& a, Mod_Int b) {
return a = _sub(a.val, b.val);
}
constexpr friend inline Mod_Int& operator *= (Mod_Int& a, Mod_Int b) {
return a = _mul(a.val, b.val);
}
constexpr friend inline Mod_Int& operator /= (Mod_Int& a, Mod_Int b) {
return a = _mul(a.val, inv(b.val));
}
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
constexpr friend inline Mod_Int& operator ^= (Mod_Int& a, _Tp p) {
return a = _pow(a.val, p);
}
constexpr friend inline bool operator == (Mod_Int a, Mod_Int b) {
return a.val == b.val;
}
constexpr friend inline bool operator != (Mod_Int a, Mod_Int b) {
return a.val != b.val;
}
constexpr Mod_Int& operator ++ () {
this -> val + 1 == mod ? this -> val = 0 : ++this -> val;
return *this;
}
constexpr Mod_Int& operator -- () {
this -> val == 0 ? this -> val = mod - 1 : --this -> val;
return *this;
}
constexpr Mod_Int operator ++ (int) {
Mod_Int res = *this;
this -> val + 1 == mod ? this -> val = 0 : ++this -> val;
return res;
}
constexpr Mod_Int operator -- (int) {
Mod_Int res = *this;
this -> val == 0 ? this -> val = mod - 1 : --this -> val;
return res;
}
friend std::istream& operator >> (std::istream& is, Mod_Int<mod, T, S>& x) {
T ipt;
return is >> ipt, x = ipt, is;
}
friend std::ostream& operator << (std::ostream& os, Mod_Int<mod, T, S> x) {
return os << x.val;
}
protected:
T val;
static constexpr inline T _add(T a, T b) {
return a >= mod - b ? a + b - mod : a + b;
}
static constexpr inline T _sub(T a, T b) {
return a < b ? a - b + mod : a - b;
}
static constexpr inline T _mul(T a, T b) {
return static_cast<S>(a) * b % mod;
}
template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
static constexpr inline T _pow(T a, _Tp p) {
T res = 1;
for (; p; p >>= 1, a = _mul(a, a))
if (p & 1) res = _mul(res, a);
return res;
}
};
using mint = Mod_Int<>;
constexpr mint operator ""_m (unsigned long long x) {
return mint(x);
}
constexpr mint operator ""_mod (unsigned long long x) {
return mint(x);
}
}
#include <cstdio>
#include <iostream>
#include <limits>
using namespace std;
using namespace Mod_Int_Class;
const int N = 310;
int n;
char S[N];
mint frac[N], Inv[N], ifrac[N];
mint f[N][N], g[N][N];
inline mint C(int n, int m) {
return frac[n] * ifrac[m] * ifrac[n - m];
}
signed main() {
scanf("%d%s", &n, S + 1);
for (int i = 1; i < n; ++i) g[i][i + 1] = 1;
g[n][1] = 1, frac[0] = ifrac[0] = 1;
for (int i = 1; i <= n; ++i) {
frac[i] = frac[i - 1] * i;
Inv[i] = i == 1 ? 1 : 0_mod - (mint::mod / i) * Inv[mint::mod % i];
ifrac[i] = ifrac[i - 1] * Inv[i];
}
for (int len = 3; len <= n + 1; ++len)
for (int l = 1; l <= n; ++l) {
int r = l + len - 1;
int rr = r > n ? r - n : r;
for (int k = l + 1; k < r; ++k) {
int kk = k > n ? k - n : k;
mint o = C(r - l - 2, k - l - 1);
g[l][rr] += g[l][kk] * g[kk][rr] * o;
if (S[l] == 'R') {
f[l][rr] += ((k - l) * g[l][kk] * g[kk][rr] + g[l][kk] * f[kk][rr] + g[kk][rr] * f[l][kk]) * o;
}
if (S[rr] == 'L') {
f[l][rr] += ((r - k) * g[l][kk] * g[kk][rr] + g[l][kk] * f[kk][rr] + g[kk][rr] * f[l][kk]) * o;
}
}
g[l][rr] *= (S[l] == 'R') + (S[rr] == 'L');
}
mint sum = 0;
for (int i = 1; i <= n; ++i) sum += f[i][i];
sum *= ifrac[n];
printf("%d\n", sum.raw());
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18508808。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。