P7496 干杯!再会! 题解
n\le 8
直接枚举
写起来意外的麻烦
复杂度
#include<bits/stdc++.h>
#define N 10
#define MOD 1000000007
using namespace std;
void read(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
}
int n,invn,ans;
int a[N],b[N];
int p[N],v[N];
int ksm(int x,int k)
{
int res=1;
while(k)
{
if(k&1)res=1ll*res*x%MOD;
x=1ll*x*x%MOD;
k>>=1;
}
return res;
}
int gcd(int x,int y)
{
if(x==0)return y;
return gcd(y%x,x);
}
void dfs(int x)
{
if(x>n)
{
int pj=0;
for(int i=1;i<=n;i++)
{
int res=gcd(a[i],b[p[i]]);
pj=(pj+res)%MOD;
}
pj=1ll*pj*invn%MOD;
for(int i=1;i<=n;i++)
{
int res=gcd(a[i],b[p[i]]);
ans=(ans+1ll*(res-pj)*(res-pj)%MOD)%MOD;
}
return;
}
for(int i=1;i<=n;i++)
if(!v[i])
{
p[x]=i;
v[i]=1;
dfs(x+1);
p[x]=0;
v[i]=0;
}
}
int main()
{
scanf("%d",&n);
invn=ksm(n,MOD-2);
for(int i=1;i<=n;i++)
read(a[i]);
for(int i=1;i<=n;i++)
read(b[i]);
dfs(1);
ans=1ll*ans*invn%MOD;
printf("%d",ans);
}
n\le 100
如果你想要直接递推,比如说
所以我们把方差拆掉:
那么这题就变成了求两个东西:
1.数列中的数的平方的和
2.数列中的数的和的平方
那么我们记录两个值:
就会有
最后答案就是
这样直接做就是
下面这个代码真的是直接明了,就不写注释了。
#include<bits/stdc++.h>
#define N 110
#define MOD 1000000007
using namespace std;
void read(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
}
int n,invn,sum1,sum2,ans;
int a[N],b[N],mul[N];
int c[N][N];
int ksm(int x,int k)
{
int res=1;
while(k)
{
if(k&1)res=1ll*res*x%MOD;
x=1ll*x*x%MOD;
k>>=1;
}
return res;
}
int gcd(int x,int y)
{
if(x==0)return y;
return gcd(y%x,x);
}
int main()
{
scanf("%d",&n);
invn=ksm(n,MOD-2);
for(int i=1;i<=n;i++)
read(a[i]);
for(int i=1;i<=n;i++)
read(b[i]);
mul[0]=1;
for(int i=1;i<=n;i++)
mul[i]=1ll*mul[i-1]*i%MOD;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
c[i][j]=gcd(a[i],b[j]);
sum1=(sum1+1ll*c[i][j]*c[i][j])%MOD;
}
}
sum1=1ll*sum1*mul[n-1]%MOD;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int ii=1;ii<=n;ii++)
{
if(ii==i)continue;
for(int jj=1;jj<=n;jj++)
{
if(jj==j)continue;
int res1=c[i][j],res2=c[ii][jj];
sum2=(sum2+1ll*res1*res2)%MOD;
}
}
}
}
sum2=1ll*sum2*mul[n-2]%MOD;
sum2=(sum1+sum2)%MOD;
ans=(sum1-1ll*invn*sum2%MOD+MOD)%MOD;
ans=1ll*ans*invn%MOD;
printf("%d",ans);
}
n\le 10^6
(O(n^2)都是些奇怪的东西,就跳过了。)
然后就是想办法加快求
首先是
我们记录
也就是:
不过想要
也就是:
但是这样的话如果
最终会有:
直接预处理
最后
复杂度为
然后就是本题的难点所在,
仔细观察原式:
其中要满足两个条件
但是在计数的时候保证选的两个不同的数是难的,而选两个相同的数或者是任选两个数都是好做的。
所以这就启发我们去容斥!
具体来说我们先求出无限制下的答案,然后减去满足
有点繁琐,不过意会一下应该能明白。
我们把上面四个情况顺次记作
tot1
先把式子写出来:
观察一下发现:
想到之前求
tot2,tot3
因为它俩一模一样,所以就讲讲
(因为
再观察一下可以发现
然后就用到我们的欧拉反演了!
当然,反演的式子很简单:
咋用呢,其实就是把gcd换掉!
代入即有:
我们记录一个辅助数组
那么上式就是:
这样就可以
如果你不用欧拉反演而是继续用之前的容斥的话,复杂度就是
还是挺简单的。
tot4
没了。
然后根据
复杂度
写代码的时候还要注意一个细节,就是所有的
代码长度也就暴力的两倍
#include<bits/stdc++.h>
#define N 1000010
#define MOD 1000000007
using namespace std;
void read(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
}
int n,m,invn,sum1,sum2,ans;
int a[N],b[N],mul[N];
int ca[N],cb[N],f[N],phi[N],ga[N],gb[N];
int ksm(int x,int k)
{
int res=1;
while(k)
{
if(k&1)res=1ll*res*x%MOD;
x=1ll*x*x%MOD;
k>>=1;
}
return res;
}
int gcd(int x,int y)
{
if(x==0)return y;
return gcd(y%x,x);
}
int main()
{
scanf("%d",&n);
invn=ksm(n,MOD-2);
for(int i=1;i<=n;i++)
{
int x;
read(x);
++a[x];//要存进桶里
m=max(m,x);
}
for(int i=1;i<=n;i++)
{
int x;
read(x);
++b[x];
m=max(m,x);
}
mul[0]=1;
for(int i=1;i<=n;i++)
mul[i]=1ll*mul[i-1]*i%MOD;//前缀积
for(int i=m;i>=1;i--)
for(int j=1;j*i<=m;j++)
ca[i]+=a[j*i];//把多少个a是i的倍数算进ca[i]中
for(int i=m;i>=1;i--)
for(int j=1;j*i<=m;j++)
cb[i]+=b[j*i];
for(int i=m;i>=1;i--)
{
f[i]=1ll*ca[i]*cb[i]%MOD;
for(int j=2;j*i<=m;j++)
(f[i]+=MOD-f[j*i])%=MOD;//容斥,求出有多少gcd(a,b)=i,存进f[i]
sum1=(sum1+1ll*f[i]*i%MOD*i)%MOD;//其实是res1
sum2=(sum2+1ll*f[i]*i)%MOD;//tot1
}
sum2=1ll*sum2*sum2%MOD;
for(int i=1;i<=m;i++)
{
phi[i]=(phi[i]+i)%MOD;
//这也是phi的一种求法,这样写主要是为了体现出其容斥的内涵
int tmpa=1ll*phi[i]*ca[i]%MOD,tmpb=1ll*phi[i]*cb[i]%MOD;
(gb[i]+=tmpb)%=MOD;
(ga[i]+=tmpa)%=MOD;
sum2=(sum2-1ll*gb[i]*gb[i]%MOD*a[i]%MOD+MOD)%MOD;//tot2
sum2=(sum2-1ll*ga[i]*ga[i]%MOD*b[i]%MOD+MOD)%MOD;//tot3
for(int j=2;j*i<=m;j++)
(phi[j*i]+=MOD-phi[i])%=MOD,(gb[j*i]+=tmpb)%=MOD,(ga[j*i]+=tmpa)%=MOD;
}
sum2=(sum2+sum1)%MOD;//tot4
sum1=1ll*sum1*mul[n-1]%MOD;
sum2=1ll*sum2*mul[n-2]%MOD;
sum2=(sum1+sum2)%MOD;
ans=(sum1-1ll*invn*sum2%MOD+MOD)%MOD;
ans=1ll*ans*invn%MOD;
printf("%d",ans);
}
n\le 5\times 10^6
没有被放进去的数据范围。
这咋整啊!卡常?
其实是高维前缀和优化。
复杂度上看,高维前缀和的复杂度是 卡5000000还是很轻松的。
那么高维前缀和优化到底是啥呢?
其实就是我们想要求一个东西
那么我们可以先把
而在加的过程中我们可以把之前算过的
而这可以看作是一个多维的空间,其中每一维都是由一个质数
代码也很短,如下:
for(int i=1;i<=n;i++)f[i]=g[i];
for(int i=1;i<=p[0];i++)
for(int j=n/p[i];j>=1;j--)
f[j]+=f[j*p[i]];
那么我们原来的求
我们举例一下原来所有要
1.
2.
3.
4.
好家伙,经典
不过我们仍然有办法!
我们想,反正都是容斥,这里也用欧拉反演不好吗?
比如说:
要用
啊这怎么欧拉反演。。。
我们参考欧拉反演的式子
具体的,我们找到一个函数
这里根据狄利克雷卷积有
我们可以轻易发现
然后就没了。
复杂度
全村最快的代码
在洛谷上跑
#include<bits/stdc++.h>
#define N 5000010
#define MOD 1000000007
using namespace std;
void read(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
}
int f[N],phi[N],v[N],p[N];
int n,m,invn,sum1,sum2,ans;
int a[N],b[N],mul[N];
int ca[N],cb[N],ga[N],gb[N];
int ksm(int x,int k)
{
int res=1;
while(k)
{
if(k&1)res=1ll*res*x%MOD;
x=1ll*x*x%MOD;
k>>=1;
}
return res;
}
int gcd(int x,int y)
{
if(x==0)return y;
return gcd(y%x,x);
}
int main()
{
read(n);
invn=ksm(n,MOD-2);
for(int i=1;i<=n;i++)
{
int x;
read(x);
++a[x];
++ca[x];//这边要多一步,等价于ca[x]=a[x]
m=max(m,x);
}
for(int i=1;i<=n;i++)
{
int x;
read(x);
++b[x];
++cb[x];
m=max(m,x);
}
mul[0]=1;
for(int i=1;i<=n;i++)
mul[i]=1ll*mul[i-1]*i%MOD;
phi[1]=1;f[1]=1;
for(int i=2;i<=m;i++)
{
if(!v[i])p[++p[0]]=i,phi[i]=i-1,f[i]=1ll*i*i%MOD-1;//f[i]就是我们自己求的积性函数!
for(int j=1;j<=p[0]&&p[j]*i<=m;j++)
{
v[i*p[j]]=1;
if(i%p[j]==0)
{
phi[i*p[j]]=phi[i]*p[j];
f[i*p[j]]=1ll*f[i]*p[j]%MOD*p[j]%MOD;
break;
}
else phi[i*p[j]]=phi[i]*phi[p[j]],f[i*p[j]]=1ll*f[i]*f[p[j]]%MOD;
}
}
for(int i=1;i<=p[0];i++)
for(int j=m/p[i];j>=1;j--)
ca[j]+=ca[j*p[i]],cb[j]+=cb[j*p[i]];//高维前缀和!
for(int i=m;i>=1;i--)
{
int tmp=1ll*ca[i]*cb[i]%MOD;
sum1=(sum1+1ll*tmp*f[i])%MOD;
sum2=(sum2+1ll*tmp*phi[i])%MOD;
}
sum2=1ll*sum2*sum2%MOD;
for(int i=1;i<=m;i++)//初始化
{
gb[i]=1ll*phi[i]*cb[i]%MOD;
ga[i]=1ll*phi[i]*ca[i]%MOD;
}
for(int i=1;i<=p[0];i++)
for(int j=1;j<=m/p[i];j++)
(ga[j*p[i]]+=ga[j])%=MOD,(gb[j*p[i]]+=gb[j])%=MOD;//高维前缀和!
for(int i=1;i<=m;i++)
{
sum2=(sum2-1ll*gb[i]*gb[i]%MOD*a[i]%MOD+MOD)%MOD;
sum2=(sum2-1ll*ga[i]*ga[i]%MOD*b[i]%MOD+MOD)%MOD;
}
sum2=(sum2+sum1)%MOD;
sum1=1ll*sum1*mul[n-1]%MOD;
sum2=1ll*sum2*mul[n-2]%MOD;
sum2=(sum1+sum2)%MOD;
ans=(sum1-1ll*invn*sum2%MOD+MOD)%MOD;
ans=1ll*ans*invn%MOD;
printf("%d",ans);
}