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;
}
posted @ 2023-02-03 17:06  Aryper  阅读(36)  评论(1编辑  收藏  举报