Removing People 题解

前言

题目链接:Atcoder;洛谷。

题意简述

\(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}\)。

说了这么多,其实就是一个转移方程:

\[g_{l, r} = \sum \Big([S_l = \texttt{R}] + [S_r = \texttt{L}]\Big) \cdot g_{l, k} \cdot g_{k, r} \cdot \binom{\operatorname{dis}(l, r) - 2}{\operatorname{dis}(l, k) - 1} \]

\(f\) 的转移很类似,如果是选择 \(l\) 导致 \(k\) 被加入:

\[f_{l, r} \gets [S_l = \texttt{R}] \sum (\operatorname{dis}(l, k) \cdot g_{l, k} \cdot g_{k, r} + g_{l, k} \cdot f_{k, r} + g_{k, r} \cdot f_{l, k}) \cdot \binom{\operatorname{dis}(l, r) - 2}{\operatorname{dis}(l, k) - 1} \]

选择 \(r\) 同理有:

\[f_{l, r} \gets [S_r = \texttt{L}] \sum (\operatorname{dis}(k, r) \cdot g_{l, k} \cdot g_{k, r} + g_{l, k} \cdot f_{k, r} + g_{k, r} \cdot f_{l, k}) \cdot \binom{\operatorname{dis}(l, r) - 2}{\operatorname{dis}(l, k) - 1} \]

注意到,之所以我一直避免 \(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;
}
posted @ 2024-10-27 21:09  XuYueming  阅读(94)  评论(0)    收藏  举报