【CF585E】Present for Vitalik the Philatelist(因数容斥套路题)

点此看题面

  • 给定一个大小为(n)的可重集。
  • 问有多少种方式从(n)中取出一个元素(x)和一个子集(S),满足(x otin S)(gcd{S}>1,gcd(gcd{S},x)=1)
  • (nle5 imes10^5),值域为([2,10^7])

因数容斥

应该算是一种做滥掉的套路了吧。。。

这个套路大致就是,令(f(i))表示(d)(i)的倍数的方案数,(f'(i))表示(d=i)的方案数。

显然有(f'(i)=f(i)-sum_{i|j}f'(j)),那么只要从大到小枚举(i)做一遍就好了,且这样子做一遍的复杂度应该是(O(Vln V))的。

容斥+推式子

首先,我们发现如果(gcd{S}=1),则(gcd(gcd{S},x)=1)必然成立。

因此只需求出(gcd(gcd{S},x)=1)的方案数,然后减去(gcd{S}=1)的方案数,就可以得到题目中要求的方案数了。

于是,我们设(f(i))表示(gcd(gcd{S},x))(i)的倍数的方案数,(g(i))表示(gcd{S})(i)的倍数的方案数,并套路地定义(f'(i))(g'(i))

显然,答案就是(f'(1)-g'(1))。那么现在的问题就是如何快速计算(f(i))(g(i))

考虑用(c(i))表示(i)的倍数个数。

对于(f(i))

其方案数就是在这(c(i))个数中枚举一个数作为(x),剩余的数任选(不能为空)作为(S)

即:

[c(i) imes(2^{c(i)-1}-1) ]

对于(g(i))

枚举(S)的大小(k),那么(x)就有(n-k)种取法,暴力计算式就是:

[sum_{k=1}^{c(i)}C_{c(i)}^k imes(n-k) ]

把括号拆开,式子就变成了两部分:

[n imes sum_{k=1}^{c(i)}C_{c(i)}^k-sum_{k=1}^{c(i)}C_{c(i)}^k imes k ]

对于前半部分:

[n imes sum_{k=1}^{c(i)}C_{c(i)}^k=n imes(2^{c(i)}-1) ]

对于后半部分:

[egin{align} sum_{k=1}^{c(i)}C_{c(i)}^k imes k&=sum_{k=1}^{c(i)}frac{c(i)!}{(k-1)! imes (c(i)-k)!}\&=c(i) imessum_{k=1}^{c(i)}C_{c(i)-1}^{k-1}\&=c(i) imessum_{k=0}^{c(i)-1}C_{c(i)-1}^{k}\&=c(i) imes2^{c(i)-1} end{align} ]

于是这道题就做完了。

代码:(O(Vln V))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define V 10000000
#define X 1000000007
using namespace std;
int n,a[N+5],pw[N+5],f[V+5],g[V+5],c[V+5],S[N+5];
int main()
{
	RI i,j;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),++c[a[i]];
	for(i=1;i<=V;++i) for(j=i<<1;j<=V;j+=i) c[i]+=c[j];//求出每个数的倍数个数
	for(pw[0]=i=1;i<=n;++i) pw[i]=(pw[i-1]<<1)%X;for(i=V;i;--i)
	{
		f[i]=1LL*c[i]*(pw[c[i]-1]-1)%X,g[i]=(1LL*n*(pw[c[i]]-1)-1LL*c[i]*pw[c[i]-1]%X+X)%X;//计算f和g
		for(j=i<<1;j<=V;j+=i) f[i]=(f[i]-f[j]+X)%X,g[i]=(g[i]-g[j]+X)%X;//因数容斥
	}return printf("%d
",(f[1]-g[1]+X)%X),0;//容斥计算答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF585E.html