6887: 游戏
时间限制: 1 Sec 内存限制: 512 MB
提交: 217 解决: 61
[提交] [状态] [讨论版] [命题人:admin]
题目描述
九条可怜是一个热爱游戏的女孩子,她经常在网上和一些网友们玩一款叫做《僵尸危机》游戏。
在这款游戏中,玩家们会需要在成为僵尸之前与黑恶势力斗智斗勇,逃离被病毒感染的小岛。但是黑恶势力不会让玩家轻易得逞,他会把一些玩家抓走改造成僵尸。变成僵尸的玩家会攻击其他的玩家,被攻击的玩家会被”感染”,成为病毒的潜在宿主。
具体来说,游戏开始时,所有的玩家会获得一个L∼R的编号(如果一共有R−L+1个玩家),不同的玩家的编号不同。
游戏分轮次进行,在每一轮中一次会发生这样的事情。
•如果所有当前所有的正常人都已经被感染,那么游戏结束。
•不然,黑恶势力会在当前的正常人(包括被感染的人)中等概率随机一个改造成僵尸。
•被改造成僵尸的玩家会攻击所有编号是他的倍数的玩家,使得他们被感染。
九条可怜现在想知道,这个游戏期望会进行多少轮?这个答案可能是一个实数,她想让你给出期望轮数乘上(R−L+1)!以后的结果,这个结果可能很大,请对109+7取模后输出。
输入
第一行输入两个整数 L, R 表示编号范围。
输出
一个整数,表示期望进行的轮数。
样例输入
2 4
样例输出
16
提示
• 2 3 4, 轮数是 2。
• 3 2 4, 轮数是 2。
• 4 2 3, 轮数是 3。
• 4 3 2, 轮数是 3。
• 2 4 3, 轮数是 3。
• 3 4 2, 轮数是 3。
每种情况的概率都是 1/6,于是期望轮数就是 (2 + 2 + 3 + 3 + 3 + 3)/6 =8/3。
乘上 3! = 6 以后就是 16 。
对于 20% 的数据,R − L + 1 ≤ 8。
对于另 10% 的数据,L = 1。
对于另 10% 的数据,L = 2。
对于另 30% 的数据,L ≤ 200。
对于 100% 的数据,1 ≤ L ≤ R ≤ 107 。
来源/分类
小结:数论组合计数+推公式+线性筛。
题解:
首先,题目让求的期望是假设共出现ans次,那么期望是ans/(排列所有情况的次数)*(r-l+1)!(阶乘),一算发现排列所有情况就是r-l+1的阶乘,其实就是求所有次数的和根本不是期望= =,原题没有这么说,但是题目这么一拐往往很多时候转不过来。
l = 1时,答案就是1的位置,因为必须要选1后,所有的编号都能被感染,所以答案为 设i为1出现的位置:
1*(r-1)!+2*(r-1)!+......4*(r-1)! 将(r-1)!提出来后,得到公式:r!*(n+1)/2。
l != 1时,可以用类似筛法的算法,找到前面区间没有其除数的所有数,把它和它的倍数标记总数为sum,那么如果想要所有的编号都被感染,则这些数必须全被选中才能保证,既我们只需要找到这些数中最后一个数的位置,这个位置确定后,我们就可以求出这个位置对答案的贡献,最后所有位置的总和即为最终答案,因为有sum个数,所有从位置sum开始到r都有可能出现,下面我们来计算每个位置对答案的贡献,设区间内没有除数的数的最后一个数的位置为local,一共有n个数,那么i对答案的贡献为:
贡献[local] = sum * C(n-local,n-sum) * (n-local)! * (local-1)! * local。即位置local的情况为sum,后面需要从剩下数n-sum中选择n-local个数,并且这n-local有(n-local)!种情况,前面又(local-1)!种情况。
将C组合数拆开一化简得到最后的公式为:sum*(n-sum)*(local)!/(local-sum)!。
这里的所有除我们都用*它的逆元表示,从大佬哪里学了个求逆元的方法,先简单地求出阶乘,再快速幂算出n!的逆元,然后倒着推出阶乘的逆元,即inv((i−1)!=inv(i!)⋅i%mod,这样可以极大地减小取模的常数,在这种卡常题中特别有用。
代码部分:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define IO ios::sync_with_stdio(false),cin.tie(0)
#define FIN freopen("D://code//in.txt", "r", stdin)
#define ppr(i,x,n) for(int i = x;i <= n;i++)
#define rpp(i,n,x) for(int i = n;i >= x;i--)
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 1e7+10;
const double pi = acos(-1);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll qow(ll x,ll k) {ll res=1;for(;k;k>>=1,x=x*x%mod)if(k&1)res=res*x%mod;return res;}
inline int read() {//读入挂
int ret = 0, c, f = 1;
for(c = getchar(); !(isdigit(c) || c == '-'); c = getchar());
if(c == '-') f = -1, c = getchar();
for(; isdigit(c); c = getchar()) ret = ret * 10 + c - '0';
if(f < 0) ret = -ret;
return ret;
}
ll fac[maxn],inv[maxn],vis[maxn] = {0};
ll n,ans,l,r,num,sum;
int main(){
scanf("%lld%lld",&l,&r);
num = r-l+1,sum = 0;
//num表示共有多少个数,sum表示区间里没有因子的数的个数,表示必选的个数。
fac[0] = 1;ans = 0;
ppr(i,1,r){fac[i] = i*fac[i-1]%mod;}//求阶乘
inv[r] = qow(fac[r],mod-2);//费马小求逆元
rpp(i,r,1){inv[i-1] = inv[i]*i%mod;}//然后倒推极大的解决了卡常问题
if(l == 1){ans = (fac[r]*(r+1)%mod*qow(2,mod-2)%mod);printf("%lld\n",ans);return 0;}
//除2用逆元表示相当于乘2的逆元
for(int i=l;i<=r;i++)
{
if(!vis[i])
{
sum++;
for(int j=i<<1;j<=r;j+=i) vis[j]=1;
}
}
//枚举每个最后一个区间无因子数出现的位置,因为有sum个所以要从sum开始。
ppr(i,sum,num)
{
ans += (sum*fac[num-sum]%mod*inv[i-sum]%mod*fac[i]%mod);//化简后得到的式子
ans %= mod;
}
printf("%lld\n",ans);
return 0;
}