NOI online R3 T3 优秀子序列

题意:

洛谷链接

给你一个长度为 \(n\) 的序列,让你找出序列所有满足条件的子集(包括空集)的价值和,一个子集满足条件当其中的任意两个元素按位与等于 \(0\),即 \(a\&b=0\),只有一个元素的集合或空集都满足条件。

一个满足条件的集合的价值为:\(\varphi (1+\)集合中所有元素的和 \()\) 。答案对1e9+7取模。

由于是考试题,列出数据范围:

  • 对于 \(10\%\) 的数据,保证 \(a_i\le 1\)
  • 对于 \(30\%\) 的数据,保证 \(a_i\le 1000\)
  • 对于 \(60\%\) 的数据,保证 \(a_i\le 30000\)
  • 另有 \(10\%\) 的数据,保证 \(n\le 20\)
  • 对于 \(100\%\) 的数据,保证 \(1\le n\le 10^6\)\(0\le a_i\le 2\times 10^5\)

我们注意到,一个满足条件的子集没有任意两个数异或值大于零,这说明二进制的每一位,集合中最多有一个数在该位置上有值。如果集合中包含了这个数,就相当于占用了这几位,就是将状压后的每一位看成花费做背包。

具体的,可以设 \(f[i][j]\) 表示状态为,选择前 \(i\) 个数和为 \(j\) 的方案数,因为元素和的二进制每一位最多是 \(1\),和也不会超过最大位。最后价值和就可以枚举集合元素的和,然后预处理出来 \(\varphi\) 值乘上方案数。背包第一维滚掉。

然后发现一个十分令人头大的事,对于前面 \(60\%\) 的部分分,\(n\) 的值一直是 \(10^6\),乘上 \(1000\) 就已经爆的很惨了。然后仔细想想,如果是相同值的元素,在集合中肯定只会出现一次(除非是 \(0\)),因为出现两次的话按位与就不是零了,所以我们把相同值的元素压缩到一起来考虑,每次计算价值时加上它的数量。

转移背包时,枚举每个值,然后枚举所有二进制位包含它的集合,\(n^2\) 的复杂度由于常数小可以获得 \(60\) 分的成绩。

当你在考虑 \(n=20\) 怎么敲暴力时,你发现用不着,前面的情况判一下如果某个值出现次数为 \(0\) 就可以直接 \(continue\),枚举二进制集合最多只会枚举 \(20\) 次,直接顺带解决了。

\(70 -> 100\),只需要在枚举集合的时候将枚举完再判断改为枚举子集就可以,枚举 \(u\) 子集的代码:

for(int i=u; i; i=(i-1)&u) f[u] += f[i] * a[u^i];

复杂度证明,一个数二进制上如果有 \(i\)\(1\),那么它子集数量为 \(2^i\)\(n\) 位的数一共有 \(C_n^i\) 个这样的数,那么所有 \(i\) 位有值的数价值和为 \(C_n^i\times 2^i\)

\(C_n^0\times 2^0+C_n^1\times 2^1+C_n^2\times 2^2+...+C_n^n\times 2^n\)

然后发现这东西等于 \((1+2)^n\)。所以复杂度是 \(3^n\)。常数小点可以过去,像我的大常数代码得加点if才能过。

然后好像用多项式科技可以达到 \(2^n\times n^2\) ,这就涉及到我的知识盲区了。。。

坑的话,注意特判0,因为0可以选多个,最后要将答案乘上 \(2^{a_0}\)\(a_0\)为0的出现次数。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define QWQ cout<<"QwQ"<<endl;
#define ll long long
#include <vector>
#include <queue>
#include <stack>
#include <map>
#define R register
using namespace std;
const ll N=301010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
const ll mp=1000000007;

int n,m,mx;
ll a[N];
ll f[N];
ll ans;

inline ll read() {
	ll sum1 = 0, f1 = 1; char c = getchar();
	while(c<'0' || c>'9') { if(c=='-') f1 = -1; c = getchar(); }
	while(c>='0'&&c<='9') { sum1 = sum1 * 10 + c - '0'; c = getchar(); }
	return sum1 * f1;
}

ll p[N],vis[N],cnt;
ll phi[N];

void qiu(ll h) {
	phi[1] = 1;
	for(ll i=2; i<=h; i++) {
		if(!vis[i]) {
			p[++cnt] = i;
			phi[i] = i-1;
		}
		for(ll j=1; j<=cnt && i*p[j]<=h; j++) {
			vis[ i*p[j] ] = 1;
			if(i%p[j]==0) {
				phi[ i*p[j] ] = phi[i] * p[j];
				break;
			}
			phi[ i*p[j] ] = phi[i] * (p[j]-1);
		}
	}
}

int main() {
	qiu(N-100);
	int x;
	n = read();
	for(R int i=1;i<=n;i++) {
		x = read(); m = max(m,x);
		a[x]++;
	}
	mx = 1;
	while(mx<=m) mx <<= 1; mx--;
	f[0] = 1;
	for(R int i=1;i<=mx;i++) {
		if(!a[i]) continue;
		R int bu = mx ^ i;
		for(R int k=bu; ; k=(k-1)&bu) {
			if(f[k]) (f[i^k] += f[k] * a[i] %mp) %= mp;
			if(!k) break;
		}
	}
	for(R int i=0;i<=mx;i++) (ans += phi[i+1] * f[i] %mp) %= mp;
	while(a[0]--) ans = ans * 2 %mp;
	cout<<ans;
	return 0;
}

考场上没做出来,好菜啊。

posted @ 2020-05-25 21:51  maple276  阅读(262)  评论(0编辑  收藏  举报