Description
Solution
这道题 (CSP) 前就计划着做来着,结果一直咕咕咕了,现在来补一下吧。
当时点开这道题主要是被题目吸引进来的,结果发现出题人是标题党,差评!
好吧,下面我们在来分析一下这道题。
我们首先考虑最优情况,其实这个是比较好想的。
不难发现,点击编号较小的开关对于编号较大的开关是没有影响的,所以我们从大到小枚举每一个灯,如果这个灯开着,就关上,并把它的约数全部操作一遍。
这样一来,就是最少的操作次数了。
再来看随机次数怎么计算,这个就要动态规划了,这个状态定义的非常巧妙。
我们设 (f_i) 表示从需要按 (i) 个开关到需要按 (i - 1) 个开关期望操作次数。
那么有转移方程:
[f_i = frac{i}{n} + frac{n - i}{n} (f[i] + f[i + 1] + 1)
]
有 (frac{i}{n}) 的概率按到需要按的开关,贡献为 1,有 (frac{n - i}{n}) 的概率按到错误的键,这时我们想要转移到 (f_{i - 1}) 的话,就必须先从 (f_{i + 1}) 转移到 (f_i),再从 (f_i) 转移到 (f_{i - 1}),所以要加上 (f_i) 和 (f_{i + 1})。
再把这个式子化简一下(开括号再移项),得:
[f_i = frac{(n - i)f_{i + 1} + n}{i}
]
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
const ll mod = 100003;
const ll N = 1e5 + 10;
ll n, k, cnt;
ll a[N], f[N];
inline ll qpow(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
signed main(){
scanf("%lld%lld", &n, &k);
for(ll i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
for(ll i = n; i >= 1; --i){
if(a[i]){
cnt++;
for(ll j = 1; j * j <= i; ++j)
if(i % j == 0){
a[j] ^= 1;
if(j * j != i) a[i / j] ^= 1;
}
}
}
for(ll i = n; i >= 1; --i)
f[i] = ((n - i) * f[i + 1] % mod + n) % mod * qpow(i, mod - 2) % mod;
ll ans = 0;
if(cnt <= k) ans = cnt;
else{
ans = k;
for(ll i = cnt; i > k; --i) ans = (ans + f[i]) % mod;
}
for(ll i = 1; i <= n; ++i)
ans = ans * i % mod;
printf("%lld
", ans);
return 0;
}