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  阅读(10)  评论(0编辑  收藏  举报