UPC-6887&[JXOI2018]游戏(好题)

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 。

来源/分类

江西OI2018 

小结:数论组合计数+推公式+线性筛。

题解:

         首先,题目让求的期望是假设共出现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;
}

猜你喜欢

转载自blog.csdn.net/Pandapan1997/article/details/81353222