【BZOJ4872】【SHOI2017】分手是祝愿 期望DP
题目大意
有\(n\)盏灯和\(n\)个开关,初始时有的灯是亮的,有的灯是暗的。按下第\(i\)个开关会使第\(j\)盏灯的状态被改变,其中\(j|i\)。每次你会随机操作一个开关,直到可以通过不多于\(k\)次操作使所有灯都灭掉,然后按照操作次数最小的方案操作。求期望的操作次数\(\times n!~mod~100003\)。
\(1\leq n\leq 100000,0\leq k\leq n\)
题解
首先不能通过操作任意个不同的开关使得灯的状态不变,因为最大那个开关对应的灯的状态一定会改变。
所以我们每次操作亮着的灯中编号最大的那盏对应的开关,直到所有灯都灭掉。这个操作步骤一定是最优步骤。记下操作次数\(num\)。
设\(f_i\)为\(i\)盏灯变成\(i-1\)盏灯期望操作次数,有:
\[\begin{align}
f_i&=\frac{i}{n}+\frac{n-i}{n}(1+f_{i+1}+f_i)\\
\frac{i}{n}f_i&=1+\frac{n-i}{n}f_{i+1}\\
f_i&=\frac{n+(n-i)f_{i+1}}{i}
\end{align}
\]
特殊的,\(f_{n+1}=0\)
最后把答案乘上\(n!\)
时间复杂度:\(O(n\sqrt n)\)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
ll p=100003;
ll fp(ll a,ll b)
{
ll s=1;
while(b)
{
if(b&1)
s=s*a%p;
a=a*a%p;
b>>=1;
}
return s;
}
int a[100010];
ll f[100010];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
int i,j;
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
int num=0;
for(i=n;i>=1;i--)
if(a[i])
{
num++;
for(j=1;j*j<=i;j++)
if(i%j==0)
{
a[j]^=1;
if(j*j!=i)
a[i/j]^=1;
}
}
ll s=1;
f[n+1]=1;
for(i=n;i>=1;i--)
f[i]=(n+(n-i)*f[i+1]%p)%p*fp(i,p-2)%p;
if(num<=k)
s=num;
else
{
s=0;
for(i=num;i>k;i--)
s=(s+f[i])%p;
s=(s+k)%p;
}
for(i=1;i<=n;i++)
s=s*i%p;
printf("%lld\n",s);
return 0;
}