P3750 [六省联考 2017] 分手是祝愿
处理出来一共有个多少的要摁的开关 (最优的方法是摁多少次)
我们可以先从 \(k\) 入手,从后往前扫,只要遇到 \(1\) 的位置就操作,并更新编号为 \(i\) 的约数的点
-
一个点不会被操作 \(2\) 次以上,因为 \(2\) 次操作相当于没操作
-
操作 \(i\) 不会影响到比ii大的数
-
所以从后往前扫,若遇到 \(1\) 不操作,那么前面的操作也不会改变这个 \(1\) ,所以必须操作
这时我们设需要摁的总开关数为 \(cnt\)。
(借鉴自本篇博客)
接下来我们该推随机处理得式子
设 \(f[i]\) 表示从 \(i\) 个需要按的开关到 \(i-1\) 个需要按的开关的期望操作次数,则我们可以得到以下式子
\(\frac{i}{n}\) 这里面的 \(i\) 表示剩下有 \(i\) 个正确的开关我们还没有摁,\(n\) 是我们总共有的开关个数。表示从 \(n\) 个开关里面选,选中正确的开关键位的概率.
则 \(\frac{n-1}{n}\) 表示按到错误的灯的概率,因为按错了,所以在之后的操作中需要将这个按键按回来,所以就多了一个需要按的键,要加 \(1\)。同时因为我们按错了,所以需要从 \(i+1\) 转移到 \(i\) ,再从 \(i\) 转移到 \(i-1\)。这就是为什么要加 \(f[i]+f[i+1]\)。
对上述式子化简
得到结果
在求出这个式子之后,我们先比较一下必须按的按键个数和 \(k\) ,如果还要小,就肯定是前者作为答案
不然直接把 \(f[cnt]+f[cnt-1]+.....+f[k+1]\) 作为答案就行,相当于把正确的开关个数从 \(cnt\) 个按到 \(k\) 个的期望值,最后再加上剩余要按得最有按键个数 \(k\) 就行。
提示
我们从 \(n\), 转移过来而不是 \(cnt\) 转移过来是为了处理 \(f[cnt+1]\)。在 \(f[n+1]\) 时我们知道值一定为 \(0\) (一共有 \(n\) 个开关,从 \(n+1\) 个开关到 \(n\) 个开关一定为 \(0\))但我们不知道 \(f[cnt+1]\) 的值,所以要从 \(f[n+1]\) 一个一个往前推。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <algorithm>
#define mod 100003
using namespace std;
long long n, k, a[5211314], cnt;
long long f[5211314], inv[5211314];
int main() {
scanf("%lld%lld", &n, &k);
inv[1] = 1;
for (int i = 2; i <= n; ++ i) {
inv[i] = ((mod - mod / i) * inv[mod % i]) % mod;
}
for (int i = 1; i <= n; ++ i) {
scanf("%lld", &a[i]);
}
for (int i = n; i >= 1; -- i) {
if (a[i] == 1) {
cnt ++;
for (int j = 1; j * j <= i; ++ j) {
if (i % j == 0) {
a[j] ^= 1;
if (j * j != i) {
a[i / j] ^= 1;
}
}
}
}
}
// cout << cnt << endl;
f[n + 1] = 0;
long long tem;
for (int i = n; i >= 1; -- i) {
tem = ((((n - i) * f[i + 1]) % mod) + n) % mod;
tem = tem * inv[i] % mod;
f[i] = tem;
}
tem = 0;
if (cnt <= k) tem = cnt;
else {
for (int i = cnt; i > k; -- i) {
tem = (tem + f[i]) % mod;
}
tem = (tem + k) % mod;
}
for (long long i = 1; i <= n; ++ i) {
tem = (tem * i) % mod;
}
printf("%lld\n", tem);
return 0;
}