CF1900 D. Small GCD

传送门:CF

前题提要:D没写出来,B贪心挂了好几发,掉大分了.难受.

其实D仔细想想还是当时自己的脑子不太清晰,其实这道题还是挺典的,甚至不久之前就做过类似的容斥方法.给个链接 D. Counting Rhyme,和本题的容斥方法可以说是一模一样(当时赛时就记起来了),但是赛时没想到枚举中间数,一直在乱搞,甚至想到了反演,简直了.


总结一下还是太菜了,没什么好说的.接下来讲讲本题.

感觉首先需要想到枚举中间数,不然简直无法下手.枚举中间数也是这种三元组的经典trick吧,虽然赛时我根本没想到.
显然我们发现三元组的贡献和a数组的顺序无关,所以考虑先对其排个序.
考虑枚举中间数 a j a_j aj,那么此时比 a j a_j aj大的数就是 n − j n-j nj个.这样后半部分的问题就解决了.接下来需要解决的是如何解决 a i a_i ai的问题.这个应该每个人都会自然而然的想到考虑 g c d gcd gcd的贡献吧(如果想不到就是题做少了).
我们将计算数对的问题转化为枚举每一个 g c d gcd gcd,然后计算 ( a i , a j ) = g c d (a_i,a_j)=gcd (ai,aj)=gcd的个数.接下来就需要考虑容斥了.直接计算上述式子是很难计算的.但是我们可以很轻易的计算出 ( a i , a j ) = k ∗ g c d (a_i,a_j)=k*gcd (ai,aj)=kgcd的个数(也就是所需 g c d gcd gcd的倍数).我们只要枚举当前 a j a_j aj的所有因子,然后使用一个桶记录一下之前该因子出现的次数即可.(因为同时有两个数具有相同因子说明他们的公因子至少为该因子的倍数)

考虑 f i f_i fi为所有三元组中 g c d gcd gcd i i i的倍数的个数. d p i dp_i dpi为所有三元组中 g c d gcd gcd i i i的个数.现在我们知道 f f f,想要求 d p dp dp,这是一个很经典的容斥问题.
考虑 d p i = f i − ∑ k = i + i ∞ d p [ k ] dp_i=f_i-\sum_{k=i+i}^\infty dp[k] dpi=fik=i+idp[k],所以我们只需要倒推,然后求出每一个 d p i dp_i dpi即可.


下面是具体的代码部分:

#include 
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
vector<int>b[maxn];int a[maxn];int cnt[maxn];
int f[maxn];
signed main() {
	int limit=1e5;
	for(int i=1;i<=limit;i++) {
		for(int j=i;j<=limit;j+=i) {
			b[j].push_back(i);
		}
	}
	int T=read();
	while(T--) {
		memset(f,0,sizeof f);memset(cnt,0,sizeof cnt);
		int n=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++) {
			for(int j=0;j<b[a[i]].size();j++) {
				//gcd的倍数的个数
				f[b[a[i]][j]]+=cnt[b[a[i]][j]]*(n-i);
				cnt[b[a[i]][j]]++;
			}
		}
		int ans=0;
		for(int i=limit;i>=1;i--) {
			for(int j=i+i;j<=limit;j+=i) {
				f[i]-=f[j];
			}
			ans+=f[i]*i;
		}
		cout<<ans<<endl;
	}
	return 0;
}

你可能感兴趣的:(c++算法,#,各类比赛,#,数论,算法)