https://www.luogu.com.cn/problem/P3750
运用贪心的策略,如果我们想要尽快关闭所有的灯,显然从编号大的到编号小的关更优
那么我们可以计算出关灯的最小次数\(cnt\)
如果\(cnt \le k\),显然答案就是\(cnt\)
如果\(cnt > k\),我们考虑\(dp\)
由于不同的键的效果是互不相同的,不存在一个键被代替的情况
设\(dp_i\)表示从最少需要按\(i\)个键转移到最少需要按\(i-1\)个键的期望按键数量
解释一下,\(\frac{i}{n}\)表示凑巧按对了的期望,\(\frac{n-i}{n}\) 是按错的概率,既然按错了就需要按回来,需要按\(dp_{i+1}\)次,这次按了\(1\)键,需要加\(1\),注意,\(dp_i\)表示从最少需要按\(i\)个键转移到最少需要按\(i-1\)个键的期望按键数量,那么我们还需要转移到最少需要按\(i-1\)个键,需要按\(dp_i\)次
推一下式子,得出递推式:
显然,我们要从\(cnt\)开始转移,但是\(dp_{cnt}\)是啥啊?
要求出\(dp_{cnt}\),我们还需要前面的值,什么时候值是可知的呢?
可以从\(dp_n=1\)开始转移(需要按\(n\)个键,瞎按也\(100\%\)按到,我的代码里用了这个)
也可以从\(dp_{n+1}=0\)开始转移(感性理解,顶多按\(n\)键,不用按就少一键)
随便挑吧!
这里有个奇怪的问题,明明只用按\(cnt\)键,我们为什么处理到了\(dp_n\)呢,它的意义何在?
其实\(dp_n\)是有意义的,因为本来只需要按\(cnt\)次,但是被\(B\)君随机瞎按按坏啦,以至于退化到需要按\(n\)键
现在就没有什么疑问了吧?
\(C++ Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define N 100005
#define p 100003
#define ll long long
using namespace std;
int n,k,cnt;
ll inv[N],dp[N];
bool close[N];
vector<int>g[N];
int main()
{
scanf("%d%d",&n,&k);
inv[1]=1;
for (int i=2;i<=n;i++)
inv[i]=(ll)(p-p/i)*inv[p%i]%p;
for (int i=1;i<=n;i++)
for (int j=i;j<=n/i;j++)
if (j==i)
g[i*j].push_back(i); else
{
g[i*j].push_back(i);
g[i*j].push_back(j);
}
for (int i=1;i<=n;i++)
scanf("%d",&close[i]);
for (int i=n;i>=1;i--)
if (close[i])
{
for (vector<int>::iterator it=g[i].begin();it!=g[i].end();++it)
close[(*it)]=!close[(*it)];
cnt++;
}
ll ans=k;
if (cnt<=k)
ans=cnt; else
{
dp[n]=1;
for (int i=n-1;i>k;i--)
dp[i]=(ll)n*inv[i]%p+(n-i)*inv[i]%p*dp[i+1]%p;
for (int i=k+1;i<=cnt;i++)
ans=(ans+dp[i])%p;
}
for (int i=1;i<=n;i++)
ans=ans*(ll)i%p;
ans=(ans%p+p)%p;
printf("%lld\n",ans);
return 0;
}
原文:https://www.cnblogs.com/GK0328/p/13455159.html