[BZOJ4872][Shoi2017]分手是祝愿(期望dp)

期望太强大了!

Source

https://www.lydsy.com/JudgeOnline/problem.php?id=4872

Solution

先考虑求出操作次数最少(也就是每个开关最多操作一次)的方案。
考虑到操作第 i i 个开关,会使状态改变的灯一定为 i 或在 i i 的前面。
因此,可以从第 n 个开关倒序到第 1 1 个开关进行讨论,当走到第 i 个开关时,如果第 i i 个灯是亮的,那么就对第 i 个开关进行操作并改变编号为 i i 的约数的灯的状态。
设最少需要 cnt 步才能使所有灯都灭掉。
cntk c n t ≤ k 时,期望操作次数为 cnt c n t
否则将问题抽象成: n n 个数,有 cnt 个数为 1 1 ,其余为 0 。每次会等概率随机地选择一个数进行取反,如果在某时刻 1 1 的个数恰好为 k 则不再随机,直接将这 k k 1 取反。求将所有数变成 0 0 的期望取反次数。
DP 。定义状态: f[i] 表示当前有 i i 1 ,将 1 1 的个数减少 1 (即变为 i1 i − 1 )的期望步数。
根据期望的线性性质,可以从 f f 得出将所有数变为 0 的期望步数:

k+i=k+1cntf[i] k + ∑ i = k + 1 c n t f [ i ]

f f 的边界为 f[n]=1
而对于一个 k<i<n k < i < n
考虑第一次等概率随机选出的数是 1 1 0
选出的数是 1 1 时,这个 1 变成 0 0 n 个数中 1 1 的数量减少 1 ,已经达到目的。概率为 in i n ,步数为 1 1
选出的数是 0 时,这个 0 0 变成 1 n n 个数中 1 的数量增加 1 1 ,这时候则需要将 1 的数量减少 1 1 后再减少 1 。概率为 nin n − i n ,步数为 1+f[i+1]+f[i] 1 + f [ i + 1 ] + f [ i ]
于是得出 f[i] f [ i ] f[i+1] f [ i + 1 ] 的关系式:
f[i]=in×1+nin×(1+f[i+1]+f[i]) f [ i ] = i n × 1 + n − i n × ( 1 + f [ i + 1 ] + f [ i ] )

这还不能达到递推的目的,因此移项,将 f[i] f [ i ] 移到左边:
f[i]=1+nii×(1+f[i+1]) f [ i ] = 1 + n − i i × ( 1 + f [ i + 1 ] )

得到 f f 的递推式。
注意最后乘上 n!

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, PYZ = 1e5 + 3;
int n, k, a[N], cnt, np = 1, f[N];
void orzpyzdalao(int x) {
    int i, S = sqrt(x); For (i, 1, S) {
        if (x % i) continue; a[i] ^= 1; if (i * i != x) a[x / i] ^= 1;
    }
}
int qpow(int a, int b) {
    int res = 1; while (b) b & 1 ? res = 1ll * res * a % PYZ : 0,
        a = 1ll * a * a % PYZ, b >>= 1; return res;
}
int main() {
    int i; n = read(); k = read(); For (i, 1, n) a[i] = read();
    Rof (i, n, 1) if (a[i]) orzpyzdalao(i), cnt++;
    For (i, 1, n) np = 1ll * np * i % PYZ;
    if (cnt <= k) return printf("%d\n", 1ll * cnt * np % PYZ), 0;
    f[n] = 1; Rof (i, n - 1, k + 1)
        f[i] = (1 + 1ll * (n - i) * qpow(i, PYZ - 2) % PYZ
            * (1 + f[i + 1]) % PYZ) % PYZ;
    int sum = k; Rof (i, cnt, k + 1) sum = (sum + f[i]) % PYZ;
    cout << 1ll * sum * np % PYZ << endl; return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值