有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;
}
}