[六省联考2017] 分手是祝愿
一、题目
二、解法
\(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}\times (f[i]+f[i+1])
\]
也就是有 \(\frac{n-i}{n}\) 的概率按错就需要倒回去重新按,我们把这个柿子移下项:
\[f[i]=\frac{n+(n-i)\times 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\n",ans);
}