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;
}
考场上没做出来,好菜啊。