一、题目
二、解法
(50) 分的做法可以一眼看出来,就是我们从后往前关灯,可以证明这样步数一定是最小的,但其实可以得 (75) 分。
其实上面的方法不止是最小的,而且是唯一的。意思是如果只看成单个灯的开关的话,一定是某个灯被按奇数次,某个灯被按偶数次,这是唯一确定的。知道了这个就可以抛弃掉那个很操蛋的修改约数了。
剩下的就是期望 (dp) 乱搞了,传统方法是定义 (f[i]) 还剩 (i) 个灯要关的期望步数,本题不能直接高斯消元,但是可以让 (f[i]=f[i-1]+b[i]) 来推 (b[i])(这种方法就不展开讲了)
还有更好的方法,把开关灯的过程定义在状态里面(其实也有先例),设 (f[i]) 表示从 (i) 个灯关到 (i-1) 个灯的期望步数,那么转移就很好写了:
[f[i]=1+frac{n-i}{n} imes (f[i]+f[i+1])
]
也就是有 (frac{n-i}{n}) 的概率按错就需要倒回去重新按,我们把这个柿子移下项:
[f[i]=frac{n+(n-i) imes f[i+1]}{i}
]
那么就可以直接 (O(n)) 递推了,算答案的时候求出最小按的次数 (cnt),如果这个次数比 (k) 小那么直接就是答案,否则从 (f[cnt]+f[cnt-1]...+f[k+1]) 求出到使用最小方案的期望步数,乘上 (n) 的阶乘即可。
#include <cstdio>
#define int long long
const int M = 100005;
const int MOD = 100003;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,cnt,ans,a[M],f[M];
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
signed main()
{
n=read();k=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=n;i>=1;i--)
{
f[i]=(n+(n-i)*f[i+1]%MOD)*qkpow(i,MOD-2)%MOD;
if(a[i])
{
cnt++;
for(int j=1;j*j<=i;j++)
if(i%j==0)
{
a[j]^=1;
if(j*j!=i) a[i/j]^=1;
}
}
}
if(cnt<=k) ans=cnt;
else
{
for(int i=cnt;i>k;i--) ans=(ans+f[i])%MOD;
ans=(ans+k)%MOD;
}
for(int i=1;i<=n;i++) ans=ans*i%MOD;
printf("%lld
",ans);
}