P2714 四元组统计
四元组统计
给定数列 \(\{ a_i \}\),题意即求满足 \(\gcd (a _ i , a _ j ,a _ k ,a _ l ) = 1\) 的四元组的个数。
其中 \(T\le 10^2\),\(n,a _ i \le 10 ^ 4\)。
这里是一个狄利克雷前缀和的小应用。
首先开桶 \(f(i)\) 表示数值为 \(i\) 的数的个数。
然后做一个狄利克雷后缀和,也就是把 \(i\) 的倍数的个数都统计到 \(f(i)\) 中。
然后我们使:
\[\binom {f(i)}{4} \rightarrow f(i)
\]
现在 \(f(i)\) 表示从为 \(i\) 的倍数的数中取 \(4\) 个的方案数,其 \(\gcd\) 一定为 \(i\) 的倍数。
现在作狄利克雷后缀差分,实际上对于每个 \(f(i)\),我们就将 \(\gcd\) 为 \(i\) 的倍数的那些方案数容斥掉了,剩下的只有 \(\gcd = i\) 的方案数了。
最后输出 \(f(1)\) 即可。
同理我们对于 \(k\) 元组或 \(\gcd = d\) 的统计也是一样的。
时间复杂度 \(O(T n \ln \ln n)\)。
代码:
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
namespace Ehnaev{
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}
}using Ehnaev::read;using Ehnaev::write;
inline void writeln(ll x) {write(x);putchar(10);}
const ll N=1e4;
ll n,cnt;
ll f[N+5],prime[N+5];
bool ff[N+5];
inline void Init() {
ff[1]=1;
for(ll i=2;i<=N;i++) {
if(!ff[i]) {prime[++cnt]=i;}
for(ll j=1;j<=cnt&&i*prime[j]<=N;j++) {
ff[i*prime[j]]=1;
if(i%prime[j]==0) {break;}
}
}
}
int main() {
Init();
while(scanf("%lld",&n)!=EOF) {
for(ll i=1;i<=N;i++) f[i]=0;
for(ll i=1;i<=n;i++) {ll x=read();f[x]++;}
for(ll i=1;i<=cnt;i++) {
for(ll j=N/prime[i];j;j--) {
f[j]+=f[j*prime[i]];
}
}
for(ll i=1;i<=N;i++) {
f[i]=f[i]*(f[i]-1)*(f[i]-2)*(f[i]-3)/24;
}
for(ll i=1;i<=cnt;i++) {
for(ll j=1;j*prime[i]<=N;j++) {
f[j]-=f[j*prime[i]];
}
}
writeln(f[1]);
}
return 0;
}