题意:给n个灯,初始化下给出每个灯的状态,给n个开关,操作开关i会使得编号是i的约数的灯的状态翻转一次,现在有一个种算法,如果当前状态下可以操作k次及以下就能使所有灯熄灭,直接操作这个次数,游戏结束,否则随机按一次开关。
分析与总结:首先根据初始化的状态,可以知道最少操作几次使得全部灯都灭,那就是从高位开始操作(这个很显然)。
然后分析下,假设现在有num个灯是开的,那么显然可以画出下面这个返祖模型。
根据这个状态转移图,可以列出方程
f
[
i
]
=
n
/
i
∗
1
+
(
n
−
i
)
/
n
∗
(
1
+
f
[
i
+
1
]
+
f
[
i
]
)
f[i]=n/i*1+(n-i)/n*(1+f[i+1]+f[i])
f[i]=n/i∗1+(n−i)/n∗(1+f[i+1]+f[i])
化简后可以得到
f
[
i
]
=
(
n
+
(
n
−
i
)
∗
f
[
i
+
1
]
)
/
i
f[i]=(n+(n-i)*f[i+1])/i
f[i]=(n+(n−i)∗f[i+1])/i。
然后判断到k次之后直接退出就好,注意前期是随机化操作,所以从n开始dp。
int a[N];
ll f[N];
ll fa[N];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
int n, k;
fa[0] = 1;
f(i, 1, N-1 )fa[i] = fa[i - 1] * i%mod;
while (cin >> n >> k)
{
f(i, 1, n)a[i] = in();
ll num = 0;
ff(i, n, 1)//n*sqrt(n)
{
if (a[i])
{
num++;
for (int j = 1;j*j <= i;j++)
{
if (i%j == 0)
{
a[j] ^= 1;
if (j*j!=i)a[i / j] ^= 1;
}
}
}
}
f[n + 1] = 0;
ll tmp = 0;
if (num <= k) tmp = num;
else {
for (int i = n;i > k;i--)
{
ll tmp2= (ll)(n - i)*f[i + 1] % mod;
tmp2 = (tmp2 + (ll)n) % mod;
tmp2 = tmp2 * quickmod(i, mod - 2,mod) % mod;
f[i] = tmp2;
if(i<=num)
tmp = (tmp + f[i]) % mod;
}
tmp = (tmp + k) % mod;
}
tmp = tmp* fa[n] % mod;
printf("%lld\n",tmp);
}
return 0;
}