关灯游戏 / 分手是祝愿
题目链接:ybt金牌导航1-1-8 / luogu P3750
题目大意
有一些从一到 n 编号灯泡与按钮。按下某个编号的按钮,它所有约数的灯都会由开变关,由关变开。
每次等概率随机操作一个开关,直到所有灯都灭掉。
但如果某个时候可以按不超过 k 个开关使所有灯灭掉,那么他会直接选操作次数最小的方法操作。
问你操作次数的期望乘 n 的阶乘在 % 100003 下的值。(一定是整数)
思路
这道题,我们会发现这个初始灯泡状态很烦,我们看看怎么搞它。
那我们先看最优怎么按。
(很明显最多要按 n n n 次,因为同一个点没必要按两次以上)
那因为按这个按钮 i i i, i i i 号灯泡也会亮起来,那我们可以发现一个东西:每个按键都不可能被其他按键的组合键替代。
那我们就直接从后往前扫一遍,如果碰到要关上的,那就一定按这个按钮,在更改前面的点的亮灭情况。
(因为前面已经确定了按和不按,而且你按更前面的点不管怎么按都无法使这个灯关上)
那我们就知道要按哪些按钮了。
那我们考虑期望 dp。
我们设 f i f_i fi 为从还有 i i i 个要按的变成 i − 1 i-1 i−1 个要按的的期望操作次数。
那首先,按到正确的有 i n \dfrac{i}{n} ni 的几率,就直接走一步,就是 1 × i n 1\times \dfrac{i}{n} 1×ni。
那按到错的就是 n − i n \dfrac{n-i}{n} nn−i 的几率,那你首先要按回来,然后再按一次,那就是 n − i n × ( f i + 1 + f i + 1 ) \dfrac{n-i}{n}\times(f_{i+1}+f_i+1) nn−i×(fi+1+fi+1)。
(里面的 1 1 1 就是花费一步)
那我们可以得到式子:
f i = 1 × i n + n − i n × ( f i + 1 + f i + 1 ) f_i=1\times \dfrac{i}{n}+\dfrac{n-i}{n}\times(f_{i+1}+f_i+1) fi=1×ni+nn−i×(fi+1+fi+1)
然后化简移项:
f i = i n + n − i n × f i + 1 + n − i n × f i + n − i n × 1 f_i=\dfrac{i}{n}+\dfrac{n-i}{n}\times f_{i+1}+\dfrac{n-i}{n}\times f_i+\dfrac{n-i}{n}\times 1 fi=ni+nn−i×fi+1+nn−i×fi+nn−i×1
f i − n − i n × f i = i n + n − i n × f i + 1 + n − i n f_i-\dfrac{n-i}{n}\times f_i=\dfrac{i}{n}+\dfrac{n-i}{n}\times f_{i+1}+\dfrac{n-i}{n} fi−nn−i×fi=ni+nn−i×fi+1+nn−i
i n × f i = i n + n − i n × f i + 1 + n − i n \dfrac{i}{n}\times f_i=\dfrac{i}{n}+\dfrac{n-i}{n}\times f_{i+1}+\dfrac{n-i}{n} ni×fi=ni+nn−i×fi+1+nn−i
i × f i = i + ( n − i ) × f i + 1 + ( n − i ) i\times f_i=i+(n-i)\times f_{i+1}+(n-i) i×fi=i+(n−i)×fi+1+(n−i)
f i = i + ( n − i ) × f i + 1 + ( n − i ) i f_i=\dfrac{i+(n-i)\times f_{i+1}+(n-i)}{i} fi=ii+(n−i)×fi+1+(n−i)
f i = ( n − i ) × f i + 1 + n i f_i=\dfrac{(n-i)\times f_{i+1}+n}{i} fi=i(n−i)×fi+1+n
那因为取模的数是一个质数,那我们可以用逆元来处理除法,小数什么就就可以不管(在取模意义下),不过要开 long long。
然后我们看答案是什么。
分两种情况:
- k k k 大于等于最少要按的次数,那就直接选最优,输出 k k k 乘 n n n 的阶乘。
- k k k 大于最少要按的次数(设为 n u m num num),那就是要从有 n u m num num 个要按的变成有 k k k 个要按的,然后再按照最优的按 k k k 次。
那就是 n ! × ( k + ∑ i = k + 1 n u m f i ) n!\times(k+\sum\limits_{i=k+1}^{num}f_i) n!×(k+i=k+1∑numfi)。
(记得要加上 k k k,就是最后按最优的走也要按 k k k 次)
然后这题就搞定了。
代码
#include<cmath>
#include<cstdio>
#define mo 100003
#define ll long long
using namespace std;
int n, k, a[100001], np[100001], tmp;
ll f[100005], re, ans;
ll ksm(ll x, ll y) {
//逆元
re = 1;
while (y) {
if (y & 1) re = (re * x) % mo;
x = (x * x) % mo;
y >>= 1;
}
return re;
}
void write(ll x) {
//得出答案,然后乘阶乘并输出
for (int i = 1; i <= n; i++)
x = (x * i) % mo;
printf("%lld", x);
return ;
}
int main() {
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = n; i >= 1; i--)//找到要按哪些点奇数次
if (a[i]) {
//找到了,更改那些会被影响的灯泡
np[++np[0]] = i;
tmp = floor(sqrt(i));
for (int j = 1; j <= tmp; j++)
if (i % j == 0) {
a[j] ^= 1;
if (i / j != j) a[i / j] ^= 1;
}
}
if (np[0] <= k) {
//可以直接走最优
write((ll)np[0]);
return 0;
}
for (int i = n; i >= 1; i--) {
//期望dp
f[i] = ((((1ll * (n - i) * f[i + 1]) % mo + 1ll * n) % mo) * ksm(1ll * i, mo - 2ll)) % mo;
}
ans = 1ll * k;//记得最后还有走 k 步
for (int i = k + 1; i <= np[0]; i++)
ans = (ans + f[i]) % mo;
write(ans);
return 0;
}