反演与筛法相关题目
把这俩放一起的原因是很多筛题都需要先反演。而且两类算法本质上都是求某个函数的值。
P2257 YY 的 GCD
题意
- 给定
,求 ,且 是质数的 有多少对。有多测 。
题解
容易想到莫反,考虑化式子:
这个看起来已经很简了,但还是会 TLE,考虑继续优化。如果先枚举
而
复杂度
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(i64 i=(s);i<=(e);i++)
#define fordown(i,s,e) for(i64 i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
#define gc getchar()
inline i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=1e7+5;
i64 n,m,mu[N],vv[N],sum[N];
vector<i64> pri;
void init(i64 n){
mu[1]=vv[1]=1;
forup(i,2,n){
if(!vv[i]){
mu[i]=-1;
sum[i]=1;
pri.push_back(i);
}
for(auto j:pri){
if(1ll*i*j>n) break;
vv[i*j]=1;
if(!(i%j)){
mu[i*j]=0;
sum[i*j]=mu[i];
break;
}else{
mu[i*j]=-mu[i];
sum[i*j]=-sum[i]+mu[i];
}
}
}
forup(i,1,n){
sum[i]+=sum[i-1];
}
}
signed main(){
int t=read();
init(1e7);
while(t--){
n=read();m=read();
i64 res=0;
for(i64 l=1,r=1;l<=min(n,m);l=r+1){
r=min(n/(n/l),m/(m/l));
res+=1ll*(sum[r]-sum[l-1])*(n/l)*(m/l);
}
printf("%lld\n",res);
}
}
P2522 [HAOI2011] Problem b
一眼莫反板子,首先简单转化成
然后数论分块即可。
转化为
复杂度
参考代码片段
const int N=5e4+5;
int a,b,c,d,k;
i64 sum[N];//莫比乌斯函数的前缀和。
int calcr(int l){
int r=min(b/(b/l),d/(d/l));
if(l<=a) r=min(r,a/(a/l));
if(l<=c) r=min(r,c/(c/l));
return r;
}
void solve(){
a=read(),b=read(),c=read(),d=read(),k=read();
a=(a-1)/k;b=b/k;c=(c-1)/k;d=d/k;
i64 res=0;
for(int l=1,r=0;l<=min(b,d);l=r+1){
r=calcr(l);
res+=1ll*(sum[r]-sum[l-1])*(b/l-a/l)*(d/l-c/l);
}
printf("%lld\n",res);
}
模拟赛神秘题目
题意
- 给定
,问有多少 正整数三元组,满足 ,且 。 。
题解
赛时一点思路没有,感觉很神秘啊。
考虑
设 \tau
) 表示
但这个没有考虑
综上,答案即为:
那么难点就规约成了计算
考虑一个经典(?)结论,
于是:
其中
容易发现我们可以维护
这样先暴力枚举
其实类似吧,但是容易发现每个
然后就维护
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(i64 i=(s);i<=(e);i++)
#define fordown(i,s,e) for(i64 i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
#define gc getchar()
inline i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=1e7+5;
i64 n,mu[N],tau[N],cntl[N],sumtau[N],sumtauodd[N];
bool vv[N];
vector<i64> pri;
void init(i64 n){
vv[1]=1;
mu[1]=tau[1]=1;
forup(i,2,n){
if(!vv[i]){
mu[i]=-1;
tau[i]=2;
cntl[i]=1;
pri.push_back(i);
}
for(auto j:pri){
if(1ll*i*j>n) break;
vv[i*j]=1;
if(i%j){
mu[i*j]=-mu[i];
tau[i*j]=2*tau[i];
cntl[i*j]=1;
}else{
mu[i*j]=0;
tau[i*j]=(tau[i]/(cntl[i]+1))*(cntl[i]+2);
cntl[i*j]=cntl[i]+1;
break;
}
}
}
forup(i,1,n){
sumtau[i]=sumtau[i-1]+tau[i];
sumtauodd[i]=sumtauodd[i-1]+(i&1)*tau[i];
}
}
i64 getsumtau(i64 x){
if(x<=1e7) return sumtau[x];
i64 res=0;
for(i64 l=1,r=0;l<=x;l=r+1){
r=x/(x/l);
res+=(r-l+1)*(x/l);
}
return res;
}
i64 work1(i64 n){
i64 res=0;
for(i64 t=1;t*t<=n;++t){
i64 w=n/(t*t),ss=0;
for(i64 l=1,r=0;l<=w;l=r+1){
r=w/(w/l);
ss+=(r-l+1)*getsumtau(w/l);
}
res+=mu[t]*ss;
}
return res;
}
i64 getsumtauodd(i64 x){
if(x<=1e7) return sumtauodd[x];
i64 res=0;
for(i64 l=1,r=0;l<=x;l=r+1){
r=x/(x/l);
res+=((r+1)/2-l/2)*((x/l+1)/2);
}
return res;
}
i64 work2(i64 n){
i64 res=0;
for(i64 t=1;t*t<=n;++t){
if(!(t&1)) continue;
i64 w=n/(t*t),ss=0;
for(i64 l=1,r=0;l<=w;l=r+1){
r=w/(w/l);
ss+=((r+1)/2-l/2)*getsumtauodd(w/l);
}
res+=mu[t]*ss;
}
return res;
}
signed main(){
init(1e7);
n=read();
i64 res=0;
res+=(work1(n/2)-(n/2))/2;
res+=(work2(n)-(n+1)/2)/2;
printf("%lld\n",res);
}
P3349 [ZJOI2016] 小星星
神秘子集反演。
题意
- 给定一棵树
和一张无向图简单连通 ,点数均为 。 - 问有多少个排列
,满足 。 ,保证至少有一解。
题解
首先有个显然的 DP,设
考虑“排列”是个非常强的限制,因为它要求
具体来说,设
而
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(i64 i=(s);i<=(e);i++)
#define fordown(i,s,e) for(i64 i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
#define gc getchar()
inline i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=20,inf=1e18;
i64 n,m,al;
vector<i64> e[N],e2[N];
i64 dp[N][N];
i64 ppcnt(int x){
i64 res=0;
while(x){
x-=x&-x;
++res;
}
return res;
}
void dfs(i64 x,i64 fa,i64 msk){
forup(y,1,n){
if(msk&(1<<(y-1))) dp[x][y]=1;
}
for(auto i:e[x]){
if(i==fa) continue;
dfs(i,x,msk);
forup(y,1,n){
if(!(msk&(1<<(y-1)))) continue;
i64 ct=0;
for(auto j:e2[y]){
if(!(msk&(1<<(j-1)))) continue;
ct+=dp[i][j];
}
dp[x][y]*=ct;
}
}
}
i64 calcg(i64 msk){
mem(dp,0);
dfs(1,0,msk);
i64 res=0;
forup(i,1,n){
if(msk&(1<<(i-1))) res+=dp[1][i];
}
return res;
}
signed main(){
n=read();m=read();
al=(1<<n)-1;
forup(i,1,m){
i64 u=read(),v=read();
e2[u].push_back(v);
e2[v].push_back(u);
}
forup(i,1,n-1){
i64 u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}
i64 res=0;
forup(i,0,al){
res+=((n-ppcnt(i))&1?-1:1)*calcg(i);
}
printf("%lld\n",res);
}
P6478 [NOI Online #2 提高组] 游戏
题意
- 给定一棵
个点( )的树,上面有一半白点一半黑点。 - 现在你要把白点和黑点两两匹配,称一个匹配
的权值为 。 - 对于
,求出权值为 的匹配个数。称两个匹配不同当且仅当同一个白点与不同的黑点匹配了。
题解
非常经典的二项式反演。
容易发现权值恰好为
设
那么如何求
然后
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
#define gc getchar()
inline int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int N=5005,inf=0x3f3f3f3f,mod=998244353;
int n,sum[2][N],a[N];
char str[N];
vector<int> e[N];
int dfn[N],Tm,sz[N];
void dfs1(int x,int fa){
dfn[x]=++Tm;
a[x]=str[x]-'0';
sum[0][dfn[x]]=sum[0][dfn[x]-1];
sum[1][dfn[x]]=sum[1][dfn[x]-1];
sum[a[x]][dfn[x]]++;
for(auto i:e[x]){
if(i==fa) continue;
dfs1(i,x);
}
}
int dp[N][N];
void dfs2(int x,int fa){
dp[x][0]=1;
sz[x]=0;
for(auto i:e[x]){
if(i==fa) continue;
dfs2(i,x);
fordown(j,sz[x],0){
fordown(k,sz[i],1){
(dp[x][j+k]+=1ll*dp[x][j]*dp[i][k]%mod)%=mod;
}
}
sz[x]+=sz[i];
}
++sz[x];
int ss=sum[a[x]^1][dfn[x]+sz[x]-1]-sum[a[x]^1][dfn[x]];
fordown(i,sz[x],1){
if(ss>=i) (dp[x][i]+=1ll*dp[x][i-1]*(ss-i+1)%mod)%=mod;
}
}
int fact[N],binom[N][N];
int f[N],g[N];
signed main(){
n=read();
fact[0]=1;
forup(i,1,n) fact[i]=1ll*fact[i-1]*i%mod;
forup(i,0,n){
binom[i][0]=1;
forup(j,1,n){
binom[i][j]=(binom[i-1][j-1]+binom[i-1][j])%mod;
}
}
scanf(" %s",str+1);
forup(i,1,n-1){
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0);
int m=n/2;
forup(i,0,m) f[i]=1ll*dp[1][i]*fact[m-i]%mod;
forup(i,0,m){
forup(j,i,m){
g[i]=(g[i]+1ll*((j-i)&1?mod-1:1)*binom[j][i]%mod*f[j]%mod)%mod;
}
printf("%d\n",g[i]);
}
}
P4859 已经没有什么好害怕的了
题意
- 有一个二分完全图,
的权值为 ,点权互不相同,称一个匹配的权值为 。 - 求权值恰为
的匹配数。 (两边的点数均为 )
题解
版。
首先若
若
复杂度
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
#define gc getchar()
inline int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int N=2005,inf=0x3f3f3f3f,mod=1e9+9;
int n,k,t;
struct Node{
int v,co;
}s[N<<1];
int dp[N],cnt[2],fact[N];
int binom[N][N];
signed main(){
n=read();k=read();
if((n-k)&1){
puts("0");
return 0;
}
t=k+(n-k)/2;
fact[0]=1;
forup(i,0,n){
binom[i][0]=1;
if(i) fact[i]=1ll*fact[i-1]*i%mod;
forup(j,1,i){
binom[i][j]=(binom[i-1][j]+binom[i-1][j-1])%mod;
}
}
forup(i,1,n){
s[i].v=read();s[i].co=0;
}
forup(i,n+1,n*2){
s[i].v=read();s[i].co=1;
}
sort(s+1,s+n*2+1,[&](Node a,Node b){return a.v<b.v;});
dp[0]=1;
forup(i,1,n*2){
++cnt[s[i].co];
if(!s[i].co){
fordown(j,cnt[1],1){
(dp[j]+=1ll*dp[j-1]*(cnt[1]-j+1)%mod)%=mod;
}
}
}
int res=0;
forup(i,t,n){
(res+=1ll*((i-t)&1?mod-1:1)*dp[i]%mod*fact[n-i]%mod*binom[i][t]%mod)%=mod;
}
printf("%d\n",res);
}
P3327 [SDOI2015] 约数个数和
题意
- 求
,其中 表示 的约数个数。 , 是测试数据组数。
题解
莫反套路题。
考虑
于是(钦定
最后一步是因为每个数在
然后就可以线性筛预处理
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(i64 i=(s);i<=(e);i++)
#define fordown(i,s,e) for(i64 i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
#define gc getchar()
inline i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=50005,inf=0x3f3f3f3f;
i64 mu[N],summu[N],tau[N],cntl[N],sumtau[N];
vector<i64> pri;
void init(i64 n){
tau[1]=1;mu[1]=1;
forup(i,2,n){
if(!tau[i]){
tau[i]=2;cntl[i]=1;
mu[i]=-1;
pri.push_back(i);
}
for(auto j:pri){
if(i*j>n) break;
if(i%j){
tau[i*j]=tau[i]*2;cntl[i*j]=1;
mu[i*j]=-mu[i];
}else{
tau[i*j]=tau[i]*(cntl[i]+2)/(cntl[i]+1);cntl[i*j]=cntl[i]+1;
mu[i*j]=0;
break;
}
}
}
forup(i,1,n){
sumtau[i]=sumtau[i-1]+tau[i];
summu[i]=summu[i-1]+mu[i];
}
}
void solve(){
i64 n=read(),m=read();
if(n>m) swap(n,m);
i64 res=0;
for(i64 l=1,r=0;l<=n;l=r+1){
r=min(n/(n/l),m/(m/l));
res+=(summu[r]-summu[l-1])*sumtau[n/l]*sumtau[m/l];
}
printf("%lld\n",res);
}
signed main(){
i64 t=read();
init(5e4);
while(t--){
solve();
}
}
P1829 [国家集训队] Crash的数字表格 / JZPTAB
题意
- 给定
,求 。 - 单组询问,
。
题解
怎么所有人都觉得这道题很简单,我觉得很复杂啊。
考虑
但是
于是有(钦定
然后容易发现
参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
#define gc getchar()
inline int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int N=1e7+5,inf=0x3f3f3f3f,mod=20101009;
int n,m,mu[N],vv[N],sum[N],summu[N];
vector<int> pri;
void init(int n){
vv[1]=mu[1]=1;
forup(i,2,n){
if(!vv[i]){
pri.push_back(i);
mu[i]=-1;
}
for(auto j:pri){
if(1ll*i*j>n) break;
vv[i*j]=1;
if(i%j){
mu[i*j]=-mu[i];
}else{
mu[i*j]=0;
break;
}
}
}
forup(i,1,n){
sum[i]=(sum[i-1]+i)%mod;
summu[i]=(summu[i-1]+1ll*mu[i]*i*i%mod)%mod;
}
}
int calcg(int d){
int res=0,nn=n/d,mm=m/d;
for(int l=1,r=0;l<=nn;l=r+1){
r=min(nn/(nn/l),mm/(mm/l));
(res+=1ll*(sum[r]-sum[l-1]+mod)*sum[nn/l]%mod*sum[mm/l]%mod)%=mod;
}
return res;
}
signed main(){
n=read();m=read();
if(n>m) swap(n,m);
init(m);
int ans=0;
for(int l=1,r=0;l<=n;l=r+1){
r=min(n/(n/l),m/(m/l));
(ans+=1ll*(summu[r]-summu[l-1]+mod)*calcg(l)%mod)%=mod;
}
printf("%d\n",ans);
}
P3768 简单的数学题
题意
- 给定
,求 。 - 防止答案过大,答案模
输出(其实对做法没有影响), 是质数。
题解
还是比较套路的。
看到
其中
那么前面
那么我们需要构造
参考代码
#include<bits/stdc++.h>
#define forup(i,s,e) for(i64 i=(s),E123123123=(e);i<=E123123123;++i)
#define fordown(i,s,e) for(i64 i=(s),E123123123=(e);i>=E123123123;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void();
#endif
using namespace std;
using i64=long long;
#define gc getchar()
i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=1e7,inf=0x3f3f3f3f;
#define ull unsigned long long
#define ui128 __uint128_t
struct Barrett{
ull d;ui128 m;
void init(ull _d){
d=_d;m=((ui128)(1)<<64)/d;
}
};
Barrett mod;
ull operator %(const ull a,const Barrett mod){
ull w=(mod.m*a)>>64;w=a-w*mod.d;
if(w>=mod.d)w-=mod.d;return w;
}
i64 ksm(i64 a,i64 b){
i64 c=1;
while(b){
if(b&1) c=1ll*a*c%mod;
a=1ll*a*a%mod;
b>>=1;
}
return c;
}
i64 n,p,phi[N+5],sumphi[N+5],inv6,inv4;
vector<i64> pri;
void init(i64 n){
phi[1]=1;
forup(i,2,n){
if(!phi[i]){
phi[i]=i-1;
pri.push_back(i);
}
for(auto j:pri){
if(1ll*i*j>n) break;
if(i%j){
phi[i*j]=phi[i]*(j-1)%mod;
}else{
phi[i*j]=phi[i]*j%mod;
break;
}
}
}
forup(i,1,n){
sumphi[i]=(sumphi[i-1]+phi[i]*i%mod*i%mod)%mod;
}
}
i64 sump2(i64 n){
n=n%mod;
return 1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod;
}
i64 sump3(i64 n){
n=n%mod;
return n*n%mod*(n+1)%mod*(n+1)%mod*inv4%mod;
}
map<i64,i64> val;
i64 calc(i64 n){
if(n<=N) return sumphi[n];
if(val.count(n)) return val[n];
i64 va=sump3(n);
for(i64 l=2,r=0;l<=n;l=r+1){
r=n/(n/l);
va=(va+p-(sump2(r)-sump2(l-1)+p)%mod*calc(n/l)%mod)%mod;
}
val[n]=va;
return va;
}
signed main(){
p=read();n=read();
mod.init(p);
inv6=ksm(6,p-2);
inv4=ksm(4,p-2);
init(min(n,N));
i64 res=0;
for(i64 l=1,r=0;l<=n;l=r+1){
r=n/(n/l);
res=(res+p+(calc(r)+p-calc(l-1))%mod*sump3(n/l)%mod)%mod;
}
printf("%lld\n",res);
}
P5325 【模板】Min_25 筛
题意
- 求
,其中 是积性函数,满足 。
题解
积性函数前缀和考虑 PN 筛(主要是我不会 Min_25 筛)。
那么先考虑构造积性函数
容易发现
大胆猜测
考虑数学归纳法。
首先对于
考虑假设对于所有
得证。
然后
注意
参考代码
#include<bits/stdc++.h>
#define forup(i,s,e) for(i64 i=(s),E123123123=(e);i<=E123123123;++i)
#define fordown(i,s,e) for(i64 i=(s),E123123123=(e);i>=E123123123;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void();
#endif
using namespace std;
using i64=long long;
#define gc getchar()
i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=1e7,mod=1e9+7;
i64 n,sphi[N+5],phi[N+5],inv6,inv2;
i64 ksm(i64 a,i64 b){
i64 c=1;
while(b){
if(b&1) c=a*c%mod;
a=a*a%mod;
b>>=1;
}
return c;
}
vector<i64> pri;
void init(i64 n){
phi[1]=1;
forup(i,2,n){
if(!phi[i]){
pri.push_back(i);
phi[i]=i-1;
}
for(auto j:pri){
if(i*j>n) break;
if(i%j){
phi[i*j]=phi[i]*(j-1)%mod;
}else{
phi[i*j]=phi[i]*j%mod;
break;
}
}
}
forup(i,1,n){
sphi[i]=(sphi[i-1]+i*phi[i]%mod)%mod;
}
}
map<i64,i64> val;
i64 sump2(i64 n){
n%=mod;
return n*(n+1)%mod*((2*n+1)%mod)%mod*inv6%mod;
}
i64 calcsg(i64 n){
if(n<=N) return sphi[n];
if(val.count(n)) return val[n];
i64 res=sump2(n);
for(i64 l=2,r=0;l<=n;l=r+1){
r=n/(n/l);
res-=((r+l)%mod)*((r-l+1)%mod)%mod*inv2%mod*calcsg(n/l)%mod;
if(res<0) res+=mod;
}
val[n]=res;
return res;
}
i64 ans;
i64 calch(i64 pk,i64 p,i64 k){
pk%=mod;p=(p-1)%mod;k=(k-1)%mod;
return k*p%mod*pk%mod;
}
void dfs(i64 v,i64 h,i64 p){
(ans+=h*calcsg(n/v)%mod)%=mod;
for(i64 rr=n/v;pri[p]*pri[p]<=rr;++p){
for(i64 c=2,nv=pri[p]*pri[p];nv<=rr;nv*=pri[p],++c){
dfs(v*nv,h*calch(nv,pri[p],c)%mod,p+1);
}
}
}
signed main(){
n=read();
init(min(n+5,N));
inv6=ksm(6,mod-2);inv2=ksm(2,mod-2);
dfs(1,1,0);
printf("%lld\n",ans);
}
Loj#6053. 简单的函数
题意
- 给定
,求积性函数 的前缀和 。 - 其中
, 表示按位异或。
题解
因为我不会 Min_25 筛所以考虑 Powerful Number 筛。先来构造
因为需要积性函数,又发现在奇质数的位置
容易发现这个也是积性函数。设
这个怎么求前缀和呢?容易发现等于
然后考虑
综上,复杂度
参考代码
#include<bits/stdc++.h>
#define forup(i,s,e) for(i64 i=(s),E123123123=(e);i<=E123123123;++i)
#define fordown(i,s,e) for(i64 i=(s),E123123123=(e);i>=E123123123;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void();
#endif
using namespace std;
using i64=long long;
#define gc getchar()
i64 read(){
i64 x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc;}
return x*f;
}
#undef gc
const i64 N=1e7,inf=0x3f3f3f3f,mod=1e9+7,inv2=5e8+4;
i64 ksm(i64 a,i64 b){
i64 c=1;
while(b){
if(b&1) c=1ll*a*c%mod;
a=1ll*a*a%mod;
b>>=1;
}
return c;
}
i64 n,phi[N+5],sphi[N+5];
vector<i64> pri;
void init(i64 n){
phi[1]=1;
forup(i,2,n){
if(!phi[i]){
pri.push_back(i);
phi[i]=i-1;
}
for(auto j:pri){
if(i*j>n) break;
if(i%j){
phi[i*j]=phi[i]*(j-1)%mod;
}else{
phi[i*j]=phi[i]*j%mod;
break;
}
}
}
forup(i,1,n){
sphi[i]=(sphi[i-1]+phi[i])%mod;
}
}
map<i64,i64> val;
i64 calcsphi(i64 n){
if(n<=N) return sphi[n];
if(val.count(n)) return val[n];
i64 res=(n%mod)*((n+1)%mod)%mod*inv2%mod;
for(i64 l=2,r=0;l<=n;l=r+1){
r=n/(n/l);
res=(res+mod-(r-l+1)%mod*calcsphi(n/l)%mod)%mod;
}
val[n]=res;
return res;
}
i64 calcG(i64 n){
if(n==0) return 0;
return (calcsphi(n)+calcG(n/2))%mod;
}
vector<i64> hp[10000];
i64 ans;
void dfs(i64 p,i64 h,i64 v){
(ans+=h*(calcsphi(n/v)+2*calcG((n/v)/2)%mod))%=mod;
for(i64 rr=n/v;pri[p]*pri[p]<=rr;++p){
for(i64 e=2,vv=pri[p]*pri[p];vv<=rr;++e,vv*=pri[p]){
dfs(p+1,h*hp[p][e]%mod,v*vv);
}
}
}
signed main(){
n=read();
init(min(n+5,N));
for(i64 i=0;;++i){
if(pri[i]>n/pri[i]) break;
hp[i].push_back(1);hp[i].push_back(0);
for(i64 j=pri[i]*pri[i],k=2;j<=n;j=j*pri[i],++k){
i64 nh=pri[i]^k,nv=pri[i]-1;
fordown(l,k-1,0){
nh=(nh+mod-hp[i][l]*nv%mod*(1+2*(pri[i]==2))%mod)%mod;
nv=nv*pri[i]%mod;
}
hp[i].push_back(nh);
}
}
dfs(0,1,1);
printf("%lld\n",ans);
}
P3704 [SDOI2017] 数字表格
题意
- 给定
,求 。 - 其中
表示斐波那契数列的第 项。此处定义 ,后面每个数等于它前面两个数相加。 ,多组询问,询问数量
题解
想了一下午带通项公式乘起来无果瞄了一眼题解发现可以直接莫反,破防了。
考虑化式子(钦定
看起来化不了了。根据套路,把
容易发现中间这坨可以暴力预处理,枚举每个数和它的倍数即可,预处理复杂度
然后单次询问可以数论分块
参考代码
#include<bits/stdc++.h>
#define forup(i,s,e) for(int i=(s),E123123123=(e);i<=E123123123;++i)
#define fordown(i,s,e) for(int i=(s),E123123123=(e);i>=E123123123;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
using pii=pair<int,int>;
#define fi first
#define se second
#define mkp make_pair
#define gc getchar()
int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int N=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
int ksm(int a,int b){
int c=1;
while(b){
if(b&1) c=1ll*a*c%mod;
a=1ll*a*a%mod;
b>>=1;
}
return c;
}
int n,m;
int mu[N],f[N],vv[N],val[N],sum[N],sinv[N];
vector<int> pri;
void init(int n){
mu[1]=vv[1]=f[1]=1;
forup(i,2,n){
f[i]=(f[i-1]+f[i-2])%mod;
if(!vv[i]){
pri.push_back(i);
mu[i]=-1;
}
for(auto j:pri){
if(1ll*i*j>n) break;
vv[i*j]=1;
if(i%j){
mu[i*j]=-mu[i];
vv[i*j]=1;
}else{
mu[i*j]=0;
vv[i*j]=1;
break;
}
}
}
forup(i,1,n) val[i]=1;
forup(i,1,n){
int inv=ksm(f[i],mod-2);
forup(j,1,n/i){
if(mu[j]==1){
val[i*j]=1ll*val[i*j]*f[i]%mod;
}else if(mu[j]==-1){
val[i*j]=1ll*val[i*j]*inv%mod;
}
}
}
sum[0]=1;
forup(i,1,n){
sum[i]=1ll*val[i]*sum[i-1]%mod;
}
sinv[n]=ksm(sum[n],mod-2);
fordown(i,n-1,1){
sinv[i]=1ll*sinv[i+1]*val[i+1]%mod;
}
sinv[0]=1;
}
void solve(){
n=read();m=read();
if(n>m) swap(n,m);
int ans=1;
for(int l=1,r=0;l<=n;l=r+1){
r=min(n/(n/l),m/(m/l));
ans=1ll*ans*ksm(1ll*sum[r]*sinv[l-1]%mod,1ll*(n/l)*(m/l)%(mod-1))%mod;
}
printf("%d\n",ans);
}
signed main(){
init(1e6);
int t=read();
while(t--){
solve();
}
}
P6271 [湖北省队互测2014] 一个人的数论
这道题让我重新认识莫反。
题意
- 给定
,求小于 且与 互质的数的 次方之和。 - 形式化地,求
- 本题中
以质因数分解 给出 ,保证 是质数。
题解
之前 zmy 看到这道题的时候跟 XD 说,XD 没看数据范围说他会多项式做法,然后看了一眼数据范围说不会了。
然后 lh 看到了直接秒了,这就是银牌爷的实力吗。
看到
根据经典结论,自然数幂的前缀和(即形如
显然
这里的
那么我们只要求出
求
复杂度
参考代码
#include<bits/stdc++.h>
#define forup(i,s,e) for(int i=(s),E123123123=(e);i<=E123123123;++i)
#define fordown(i,s,e) for(int i=(s),E123123123=(e);i>=E123123123;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void();
#endif
using namespace std;
using i64=long long;
using pii=pair<int,int>;
#define fi first
#define se second
#define mkp make_pair
#define gc getchar()
int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int mod=1e9+7;
int ksm(int a,int b){
int c=1;
while(b){
if(b&1) c=1ll*a*c%mod;
a=1ll*a*a%mod;
b>>=1;
}
return c;
}
int w,d,n,pw[1005][105],inv[1005],ans;
int f[105],nw[105],nxt[105];
void getf(int x,int y){
mem(nw,0);mem(nxt,0);
nw[0]=1;
int p=y;
forup(i,0,d+1){
if(i==x) continue;
p=1ll*p*ksm((x-i+mod)%mod,mod-2)%mod;
forup(j,0,d+1){
nxt[j]=1ll*(mod-i)*nw[j]%mod;
if(j) (nxt[j]+=nw[j-1])%=mod;
}
forup(j,0,d+1){
nw[j]=nxt[j];
nxt[j]=0;
}
}
forup(i,0,d+1){
(f[i]+=1ll*nw[i]*p%mod)%=mod;
}
}
signed main(){
d=read();w=read();
int y=0;
forup(i,0,d+1){
(y+=ksm(i,d))%=mod;
getf(i,y);
}
int n=1;
forup(i,1,w){
int p=read(),a=read();
n=1ll*n*ksm(p,a)%mod;
inv[i]=ksm(p,mod-2);
pw[i][0]=1;
forup(j,1,d+1){
pw[i][j]=1ll*pw[i][j-1]*p%mod;
}
}
int pn=1;
forup(i,0,d+1){
int val=1ll*f[i]*pn%mod;
pn=1ll*pn*n%mod;
forup(j,1,w){
if(i<=d) val=1ll*val*(mod+1-pw[j][d-i])%mod;
else val=1ll*val*(mod+1-inv[j])%mod;
}
(ans+=val)%=mod;
}
printf("%d\n",ans);
}