P3750 [六省联考2017]分手是祝愿 题解
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\n", ans);
return 0;
}