把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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

点此看题面

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

因数容斥

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

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

显然有\(f'(i)=f(i)-\sum_{i|j}f'(j)\),那么只要从大到小枚举\(i\)做一遍就好了,且这样子做一遍的复杂度应该是\(O(V\ln 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)\times(2^{c(i)-1}-1) \]

对于\(g(i)\)

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

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

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

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

对于前半部分:

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

对于后半部分:

\[\begin{align} \sum_{k=1}^{c(i)}C_{c(i)}^k\times k&=\sum_{k=1}^{c(i)}\frac{c(i)!}{(k-1)!\times (c(i)-k)!}\\&=c(i)\times\sum_{k=1}^{c(i)}C_{c(i)-1}^{k-1}\\&=c(i)\times\sum_{k=0}^{c(i)-1}C_{c(i)-1}^{k}\\&=c(i)\times2^{c(i)-1} \end{align} \]

于是这道题就做完了。

代码:\(O(V\ln 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\n",(f[1]-g[1]+X)%X),0;//容斥计算答案
}
posted @ 2020-11-30 14:35  TheLostWeak  阅读(119)  评论(0编辑  收藏  举报