[JXOI2018]游戏

[JXOI2018]游戏

有n个元素,标号l~r(r-l+1=n),进行全排列,从左至右标记,同时对于数i被标记还会去标记其他的标号为其倍数的元素,记最小的标号,标记完所有的元素为权值,问所有排列方案权值之和为多少(mod 10^9+7)(1≤l≤r≤10^7)

显然标记方法模拟极其复杂,于是寻求状态量,即发现有一些最小的数满足只有它自己能够标记它自己,这些数时必须要选的,不妨把它叫做最基本的数,于是是否标记完,即看它们是否选完,实际上,包括一些tarjan的题目也是看最关键的元素,这是一种常见的思想。

所以不难知道我们可以利用埃式筛或者欧拉筛,筛出这些最基本的数,权值即最右边的最基本的数的位置,所以需要枚举这个位置i,其次还要枚举这个位置选哪个数,再把剩下的数排列一下,即有(设最基本的数个数为k)

[ans=sum_{i=k}^nk imes P_{i-1}^{k-1} imes(n-k)!= ]

[sum_{i=k}^nk imes frac{(n-k)!(i-1)!}{(i-k+1)!} ]

预处理出阶乘及其逆元,套用公式暴力枚举即可。

参考代码:

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define yyb 1000000007
#define gzy 1000000005
#define ll long long
using namespace std;
bool vis[10000001];
int tot,jc[10000001],jv[10000001];
il int pow(int,int);
il void sieve(int,int);
int main(){
    int l,r,i,ans(0);
    scanf("%d%d",&l,&r),sieve(l,r),r-=l,++r;
    for(i=jc[0]=1;i<=r;++i)jc[i]=(ll)jc[i-1]*i%yyb;
    jv[r]=pow(jc[r],gzy),jv[0]=1;
    for(i=r;i>1;--i)jv[i-1]=(ll)jv[i]*i%yyb;
    for(i=tot;i<=r;++i)
        (ans+=(ll)tot*jc[r-tot]%yyb*jc[i-1]%yyb*jv[i-tot]%yyb*i%yyb)%=yyb;
    printf("%d",ans);
    return 0;
}
il int pow(int x,int y){
    int ans(1);
    while(y){
        if(y&1)ans=(ll)ans*x%yyb;
        x=(ll)x*x%yyb,y>>=1;
    }return ans;
}
il void sieve(int l,int r){
    int i;
    while(l<=r){
        if(!vis[l])
            for(++tot,i=l<<1;i<=r;i+=l)
                vis[i]|=true;
        ++l;
    }
}

原文地址:https://www.cnblogs.com/a1b3c7d9/p/10795556.html