首页 > 其他分享 >Removing People 题解

Removing People 题解

时间:2024-10-27 21:14:59浏览次数:1  
标签:return val People Int 题解 constexpr Removing friend Mod

前言

题目链接: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;
}

标签:return,val,People,Int,题解,constexpr,Removing,friend,Mod
From: https://www.cnblogs.com/XuYueming/p/18508808

相关文章

  • 字节跳动青训营 X 豆包MarsCode入营考核部分题解
    中等:观光景点组合得分问题小R正在研究一组观光景点,每个景点都有一个评分,保存在数组 values 中,其中 values[i] 表示第 i 个观光景点的评分。同时,景点之间的距离由它们的下标差 j-i 表示。一对景点 (i<j) 的观光组合得分为 values[i]+values[j]+i-j,也就......
  • ZZJC新生训练赛第十场题解
    先给出比赛链接:https://vjudge.net/contest/667035下面说一下难度分层:(同一难度下按字典序排序,数字为cf难度分)800:ABEG1100:D1200:C1400:H1500:F原题链接A:https://codeforces.com/contest/1850/problem/AB:https://codeforces.com/contest/1991/problem/......
  • Codeforces Round 981 (Div. 3) 题解(A-E)
    目录分析A思路代码B思路卡题原因代码C思路卡题原因codeD思路卡题原因代码E思路wa原因codeCodeforcesRound981(Div.3)分析这场整体发挥都不好,虽然题也抽象,但是对于这些题完全没必要卡这么久.正常至少能看到\(\mathbf{F}\)A思路因为边界\(n\)管辖\(\pm\),而Sak......
  • P11234 [CSP-S 2024] 擂台游戏 题解
    P11234[CSP-S2024]擂台游戏题解前言作者在考场上用了约1h把前三道题做完了,然后用了约半小时想了带\(\log\)的做法,但是我决定放手一搏去想线性的做法,于是又想了有1h之后觉得想到了正解,然后我就一直写到了考试结束,但是最终没有调出来遗憾离场,因此写个题解来纪念一下。......
  • Codeforces Round 980 (Div. 2) 题解(A-D)
    目录A思路B思路wa原因C思路wa原因代码D思路未ac原因代码CodeforcesRound980(Div.2)A思路推方程式即可,勉强算贪心吧.需要使得\({a-x}\)最小,那么\(x\)就需要最小,而满足题目条件,需要\(a-x\geb-2x\)得出\(x\geb-a\),又因为需要\(a\)最大,所以\(......
  • 题解:P2013 无线电测向
    P2013无线电测向题目省流:求两条直线交点坐标使用样例数据作出下图:(图片来自@_MRCMRC_)图中红线和紫线为灯塔与船的连线,蓝线为船的航线。由输入可以知道灯塔A、B相对于\(x\)正半轴的角度\(\theta_A\)、\(\theta_B\)(逆时针方向)和它们分别的坐标\((x_A,y_A)\)、\((x_B,......
  • DYN / 消防局的设立 / Spread of Information / 将军令 题解
    前言四倍经验:[POI2011]DYN-Dynamite;[HNOI2003]消防局的设立;[ARC116E]SpreadofInformation;将军令。题意简述给你一棵\(n\)个结点的树和点集\(S\),你要选出\(k\)个关键点\(T\),求\(\min\max\limits_{u\inS}\min\limits_{v\inT}\operatorname{dis}(u,v)\)......
  • CSP-J 2024 复赛题解
    T1数据仅有52,极小的数据范围导致这题只有一个问题:如何简短方便的去重并统计。我选择了map做法:每个输入查找map中之前是否记录过此元素,如果记录过则证明已经拥有这张牌,反之则记录并将统计数增加。代码如下:#include<bits/stdc++.h>usingnamespacestd;intn;map<stri......
  • Stema练习题:十四届蓝桥杯STEMA考试Python真题试卷题解
    来源:十四届蓝桥杯STEMA考试Python真题试卷第一套编程第四题这个程序虽然代码量不大,但综合运用了多种基础算法和数据结构:贪心策略选择窗口、模拟现实过程、线性查找最小值、效率高(时间复杂度为O(N)O(N)O(N))。题目描述:编程实现:某服务大厅同时开放3个窗口为客户办理......
  • Codeforces Round 982 div2 个人题解(A~D2)
    CodeforcesRound982div2个人题解(A~D2)Dashboard-CodeforcesRound982(Div.2)-Codeforces火车头#define_CRT_SECURE_NO_WARNINGS1#include<algorithm>#include<array>#include<bitset>#include<cmath>#include<cstdio>#in......