[六省联考2017]分手是祝愿

题目

展开
题目描述
Zeit und Raum trennen dich und mich. 时空将你我分开。

B 君在玩一个游戏,这个游戏由 nn 个灯和 nn 个开关组成,给定这 nn 个灯的初始状态,下标为从 11 到 nn 的正整数。

每个灯有两个状态亮和灭,我们用 11 来表示这个灯是亮的,用 00 表示这个灯是灭的,游戏的目标是使所有灯都灭掉。

但是当操作第 ii 个开关时,所有编号为 ii 的约数(包括 11 和 ii)的灯的状态都会被改变,即从亮变成灭,或者是从灭变成亮。

B 君发现这个游戏很难,于是想到了这样的一个策略,每次等概率随机操作一个开关,直到所有灯都灭掉。

这个策略需要的操作次数很多,B 君想到这样的一个优化。如果当前局面,可以通过操作小于等于 kk 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个策略显然小于等于 kk 步)操作这些开关。

B 君想知道按照这个策略(也就是先随机操作,最后小于等于 kk 步,使用操作次数最小的操作方法)的操作次数的期望。

这个期望可能很大,但是 B 君发现这个期望乘以 nn 的阶乘一定是整数,所以他只需要知道这个整数对 100003100003 取模之后的结果。

输入格式
第一行两个整数 n, kn,k。 接下来一行 nn 个整数,每个整数是 00 或者 11,其中第 ii 个整数表示第 ii 个灯的初始情况。

输出格式
输出一行,为操作次数的期望乘以 nn 的阶乘对 100003100003 取模之后的结果。

输入输出样例
输入 #1复制
4 0
0 0 1 1
输出 #1复制
512
输入 #2复制
5 0
1 0 1 1 1
输出 #2复制
5120
说明/提示
对于 0%0% 的测试点,和样例一模一样;
对于另外 30%30% 的测试点,n \leq 10n≤10;
对于另外 20%20% 的测试点,n \leq 100n≤100;
对于另外 30%30% 的测试点,n \leq 1000n≤1000;
对于 100%100% 的测试点,1 \leq n \leq 100000, 0 \leq k \leq n1≤n≤100000,0≤k≤n;
对于以上每部分测试点,均有一半的数据满足 k = nk=n。

思路

考虑期望,设f[i]表示从有i个正确选择变为有i-1个正确选择的期望操作次数, f [ i ] = i n + ( 1 − i n ) ∗ ( 1 + f [ i + 1 ] + f [ i ] ) f[i]=\dfrac{i}{n}+(1-\dfrac{i}{n})*(1+f[i+1]+f[i]) f[i]=ni+(1ni)(1+f[i+1]+f[i])
(n个开关,i个正确,其他n-i个会增加一个错误,需f[n+1]+f[n]次操作变到i-1)
移项 f [ i ] = 1 + ( 1 ∗ n − i ) ∗ ( f [ i + 1 ] + 1 ) i f[i]=1+\dfrac{(1*n-i)*(f[i+1]+1)}{i} f[i]=1+i(1ni)(f[i+1]+1),
f[n] = 1,把f累加一下就可以。
不要忘了乘 n ! n! n!

代码

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;
typedef long long LL;

inline void read(int &x) {
    static char c; bool flag = 0;
    while((c=getchar())<'0'||c>'9') flag |= (c=='-');
    x=c-'0';while((c=getchar())>='0'&&c<='9') x = x*10+c-'0';
    flag?x=-x:x;
}

typedef long long LL;

const int p = 100003;
int n,k,b[101000];
LL f[101000],inv[101000];
vector<int> g[101000];

void work() {
    LL ans = 0,tp = 0;
    for (int i = 1; i <= n; i++)
     for (int j = i; j <= n; j += i)
       g[j].push_back(i);         
    for (int i = n; i >= 1; i--) 
     if (b[i]) {
         for (int j = 0; j < g[i].size(); j++) b[g[i][j]] ^= 1; 
         tp++;
     }
    if (tp <= k) ans = tp; 
    else {
       f[n] = 1;
       for (int i = n-1; i > 1; i--) f[i] = (1LL+(1LL*n-i)*(f[i+1]+1)*inv[i])%p;
       for (int i = tp; i > k; i--) ans = (ans+f[i])%p;
       ans = (ans+k)%p; 
    }
    for (int i = 1; i <= n; i++) ans = (ans*i)%p; 
    printf("%lld",ans);
}

int main() {
    read(n); read(k);
    for (int i = 1; i <= n; i++) read(b[i]);
    inv[1] = 1;
    for (int i = 2; i <= n; i++) inv[i] = ((p-p/i)*inv[p%i])%p;
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值