题解 | #序列取反问题#
序列取反问题
http://www.nowcoder.com/practice/4e14684f12d04a6a9a6aaf2287493a75
题意
给定数组 满足
,特别地,
。
或
。
起初这些数均未被标记。每次可以在未标记的数中任选一个 ,并标记下标在
中的所有数。求将所有数均标记所需的期望次数。
解法1:状压DP(MLE+TLE)
我们用一个 位二进制数
表示
中元素被标记的状态(第
位的
表示
被标记,
表示
不被标记)。
用 表示标记过程中出现
状态的概率,
表示出现
状态所需的期望步数。
设 为
状态中
的个数,
为选择
时可标记的集合,则有转移方程
转移方程中出现分数,因此需要利用逆元计算。
线性递推逆元的公式为
代码
const int mod = 998244353; const int N = 22; int inv[N]; int f[1<<N], dp[1 << N]; class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @param a int整型vector * @return int整型 */ void init(int n) { inv[1] = 1; for (int i = 2; i <= n; i++) { inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; // 线性递推逆元 } } int ret(int n, vector<int>& a) { // write code here init(n); for (int mask = 0; mask < (1 << n); mask++) f[mask] = dp[mask] = 0; f[0] = 1; for (int mask = 0; mask < (1 << n); mask++) { int room = n - __builtin_popcount(mask); // 0的个数 for (int i = 0; i < n; i++) { if (mask & (1 << i)) continue; int cover = (1 << a[i]) - (1 << i); f[mask | cover] = (f[mask | cover] + 1ll * f[mask] * inv[room]) % mod; dp[mask | cover] = (dp[mask | cover] + 1ll * (dp[mask] + f[mask]) * inv[room]) % mod; } } return dp[(1 << n) - 1]; } };
复杂度分析
空间复杂度:逆元部分 ,DP部分
,共
时间复杂度:逆元部分 ,DP部分需要扫描每个子集,
,共
解法2:DFS
注意题目中的第2个性质,不难发现所有区间两两不相交。
因此可以转化为一棵树。
如数据 6,[6,4,3,4,6,6]
可以转化为
该问题即转化为:每次随机选一个点删除以它为根的子树,求将它删光所需删除次数的期望。
由于点是等概率选取的,因此可以考虑 的所有排列,依次删除(遇到已删除的点即跳过)。因此,一个子树被删除的概率是它的根的深度的倒数。
因此,我们需要求的就是所有点的倒数之和。
可以利用dfs实现。
代码
const int mod = 998244353; const int N = 22; int inv[N]; int f[1<<N], dp[1 << N]; class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @param a int整型vector * @return int整型 */ void init(int n) { inv[1] = 1; for (int i = 2; i <= n; i++) { inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; } } int dfs(int u, int dep, vector<int>& a){ int ret = inv[dep]; // 深度的倒数 for(int v=u+1; v<a[u]; v=a[v]){ // 子节点 ret = (ret + dfs(v, dep+1, a)) % mod; } return ret; } int ret(int n, vector<int>& a) { // write code here init(n); return dfs(0, 1, a); } };
复杂度分析
空间复杂度:逆元部分,DFS部分
,共
时间复杂度:逆元部分,DFS部分
,共
解法3:差分
我们可以发现,点 对下标在
中的所有点的深度均有
点贡献。
因此我们可以维护一个数组 ,对于每个
,将
增加
,
减少
。最后再对
做一次前缀和,便可以直接得到每个点的深度了。
代码
const int mod=998244353; const int N=200005; int inv[N]; class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int整型 * @param a int整型vector * @return int整型 */ void init(int n){ inv[1] = 1; for(int i = 2; i <= n; i++){ inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; } } int ret(int n, vector<int>& a) { // write code here init(n); vector<int> b(n + 1, 1); for(int i = 0; i < n; i++){ --b[a[i]]; //贡献的差分 } for(int i = 1; i < n; i++){ b[i] += b[i - 1]; // 前缀和 } int ans = 0; for(int i = 0; i < n; i++){ ans = (ans + inv[b[i]]) % mod; } return ans; } };
复杂度分析
空间复杂度:逆元部分,差分部分
,共
时间复杂度:逆元部分,差分部分
,共