[JXOI2018]游戏 ---- 排列组合计数+筛法

题目描述

九条可怜是一个热爱游戏的女孩子,她经常在网上和一些网友们玩一款叫做《僵尸危机》游戏。
在这款游戏中,玩家们会需要在成为僵尸之前与黑恶势力斗智斗勇,逃离被病毒感染的小岛。但是黑恶势力不会让玩家轻易得逞,他会把一些玩家抓走改造成僵尸。变成僵尸的玩家会攻击其他的玩家,被攻击的玩家会被”感染”,成为病毒的潜在宿主。
具体来说,游戏开始时,所有的玩家会获得一个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

做法:这里解释一下 2 4 3这个样例为什么是3,第一轮,先把2感染,让后2的倍数,4也被感染了,但是在第二轮中,4又被选中为僵尸,所以游戏进行到第三轮,把3感染后,结束游戏。理由(题目描述中的"不然,黑恶势力会在当前的正常人(包括被感染的人)中等概率随机一个改造成僵尸")。

然后这就是纯的数学,排列组合计数的了。就像在做高中数学题一样

分两种情况讨论:

一、当l = 1的时候,n = r  比如:

n = 3时

1 2 3,1轮

1 3 2,1轮

2 1 3,2轮

3 1 2,2轮

2 3 1,3轮

3 2 1,3轮

我们发现1所在的位置就是轮数

那么答案ans = n*(n+1)/2 * (n-1)!

为什么呢,解释一下除了1之外的n-1个数的全排列就是(n-1)!

然后我们把1插入到这n-1个数的n个位置,这n个位置的期望分别是1,2,3,…… n

所以ans = n*(n+1)/2 * (n-1)!

二、当l   != 1的时候  比如:

2 3 4, 轮数是 2
3 2 4, 轮数是 2
4 2 3, 轮数是 3
4 3 2, 轮数是 3
2 4 3, 轮数是 3
3 4 2, 轮数是 3

我们发现2和3在所有情况中都是必选

接下来我们酱紫分析,我们设第i轮后,这局游戏结束,必选的数有sum个

我们就可以看看除了必选的外,有没必要选的数有几种情况。

通过分析,即C(n-sum,n-i)*(n-i)! 是没必要选的数有几种

sum是必选的有几种,

那么(i-1)!就是i前面的数有几种

所以第i次的轮数为f[i] = (i-1)! * sum * C(n-sum,n-i)*(n-i)!

我们又发现,依据游戏中最少进行的轮数是sum轮,最多是n轮

所以 就是我们的答案

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 1e7+5;
int fat[maxn];
int inv[maxn];
bool vis[maxn];
//inline int read()
//{
//    char x;
//    while((x = getchar())>'9' || x<'0');
//    int u = x-'0';
//    while((x = getchar())<='9' && x>='0') u = (u<<3)+(u<<1)+x-'0';
//    return u;
//}
int qpow(int a,int b)
{
    int tmp = 1;
    while(b)
    {
        if(1&b) tmp = (1LL*tmp*a)%mod;
        a = (1ll*a*a)%mod;
        b>>=1;
    }
    return tmp;
}
int main()
{
    #ifdef LOCAL_FILE
    freopen("in.txt","r",stdin);
    #endif // LOCAL_FILE
    int l,r,n;
//    l = read();
//    r = read();
    scanf("%d %d",&l,&r);
    n = r-l+1;
    fat[0] = 1;
    int inv2 = qpow(2,mod-2);
    for(int i=1;i<=r;i++)
        fat[i] = (1ll*fat[i-1]*i)%mod;
    inv[r] = qpow(fat[r],mod-2);
    for(int i=r;i;i--)
        inv[i-1] = (1ll*inv[i]*i)%mod;
    int ans = 0;
    if(l == 1)
    {
        ans = 1ll*fat[n]*(n+1)%mod*inv2%mod;
        printf("%d\n",ans);
        return 0;
    }
    int sum = 0;
    for(int i=l;i<=r;i++)
    {
        if(!vis[i])
        {
            sum++;
            for(int j=i<<1;j<=r;j+=i) vis[j] = 1;
        }
    }
    for(int i=sum;i<=n;i++)
    {
        ans = (ans + 1ll*i*sum%mod*fat[n-sum]%mod*inv[i-sum]%mod*fat[i-1]%mod)%mod;
    }
    printf("%d\n",ans);
    return 0;
}

PS:顺便借助这个题,了解和复习了一下输入外挂和inline关键字

还有,过多的开long long数组,会增大时间复杂度,比较long long的一些比较和运算相比int还是要费时的

C++中的inline用法

猜你喜欢

转载自blog.csdn.net/m0_37624640/article/details/81427712