【ybt金牌导航1-1-8】【luogu P3750】关灯游戏 / 分手是祝愿

关灯游戏 / 分手是祝愿

题目链接: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 i1 个要按的的期望操作次数。

那首先,按到正确的有 i n \dfrac{i}{n} ni 的几率,就直接走一步,就是 1 × i n 1\times \dfrac{i}{n} 1×ni
那按到错的就是 n − i n \dfrac{n-i}{n} nni 的几率,那你首先要按回来,然后再按一次,那就是 n − i n × ( f i + 1 + f i + 1 ) \dfrac{n-i}{n}\times(f_{i+1}+f_i+1) nni×(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+nni×(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+nni×fi+1+nni×fi+nni×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} finni×fi=ni+nni×fi+1+nni
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+nni×fi+1+nni
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+(ni)×fi+1+(ni)
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+(ni)×fi+1+(ni)
f i = ( n − i ) × f i + 1 + n i f_i=\dfrac{(n-i)\times f_{i+1}+n}{i} fi=i(ni)×fi+1+n

那因为取模的数是一个质数,那我们可以用逆元来处理除法,小数什么就就可以不管(在取模意义下),不过要开 long long。

然后我们看答案是什么。
分两种情况:

  1. k k k 大于等于最少要按的次数,那就直接选最优,输出 k k k n n n 的阶乘。
  2. 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+1numfi)
    (记得要加上 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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/112641031