NOI Online #3 提高组 T3 优秀子序列

原题链接

简洁题意

要求从 A A A选出子集 B B B使得 B B B中没有两个数按位与等于 0 0 0。每个子集 B B B的价值是集合内所有数的和加一的 ϕ \phi ϕ值。求所有子集 B B B的价值之和。

分析

可以思考,因为要知道所有的集合 B B B不容易,所以考虑计算价值相同的 B B B的个数。设 f i f_i fi表示所有值为 i i i的子集 B B B的数量,我们算出 f i f_i fi后乘上 p h i i + 1 phi_{i+1} phii+1就是这个方案的价值和,把每个价值和加在一起就是答案。考虑怎么计算这个 f f f,我们可以把 f f f拆成两部分,然后一部分直接用 f j f_j fj,另一部分用集合 A A A的一个数来补上就行。因为题目要 B B B中所有数的按位与均等于 0 0 0,所以不会出现有两个数的某一位都是 1 1 1的情况。所以我们在拆 i i i的时候可以把 i i i中为 1 1 1的位分成两半来计算。为了方便处理,我们把 n u m x num_x numx表示 a j a_j aj中值为 x x x的个数, s s s表示 i i i分出来的一个数, t t t为剩余的部分,为了保证不重复计算,要求 s > = t s>=t s>=t,根据乘法原理,则有状态转移方程 f i + = f s × n u m t f_i+=f_s\times num_t fi+=fs×numt。可是这样就有一个问题:这些数的和的上限很大,内存装不下啊!不用担心,因为我们选出来的数不会有两位都是 1 1 1的情况,所以选出来的和自然小于 2 ⌊ log ⁡ 2 ( max ⁡ { a i } ) + 1 ⌋ 2^{\lfloor\log_2(\max\{a_i\})+1\rfloor} 2log2(max{ ai})+1,以 2 ⌊ log ⁡ 2 ( max ⁡ { a i } ) + 1 ⌋ 2^{\lfloor\log_2(\max\{a_i\})+1\rfloor} 2log2(max{ ai})+1为上限即可。但是这样就会出现一个情况: a i = 0 a_i=0 ai=0算漏了!其实 a i a_i ai等于 0 0 0时用不用都没关系,根据乘法原理,所以答案要乘上 2 n u m 0 2^{num_0} 2num0。至于筛法求 p h i phi phi(就是在筛质数时筛 p h i phi phi函数)和求一个集合的子集(要拆 i i i),大家应该都会吧?

代码

#include<bits/stdc++.h>
using namespace std;
const int NN=300000,P=1e9+7;
int num[NN],phi[NN],f[NN];
bool vis[NN];
int main()
{
    
    
	int n,maxx=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
    
    
		int x;
		scanf("%d",&x);
		num[x]++;
		maxx=max(maxx,x);
	}
	int m=0;
	while((1<<m)<=maxx)
		m++;
	phi[1]=1;
	for(int i=2;i<=(1<<m);i++)
		if(!vis[i])
			for(int j=i;j<=(1<<m);j+=i)
			{
    
    
				vis[j]=true;
				if(!phi[j])
					phi[j]=j;
				phi[j]=phi[j]/i*(i-1);
			}
	f[0]=1;
	for(int i=1;i<=(1<<m);i++)
		for(int s=i;;s=(s-1)&i)
		{
    
    
			int t=i^s;
			if(s<t)
				break;
			f[i]=(f[i]+1ll*f[t]*num[s])%P;
		}
	int ans=0;
	for(int i=0;i<=(1<<m);i++)
		ans=(ans+1ll*f[i]*phi[i+1])%P;
	for(int i=1;i<=num[0];i++)
		ans=ans*2ll%P;
	printf("%d",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44043668/article/details/109165796
今日推荐