P6570 [NOI Online #3 提高组]优秀子序列

P6570 [NOI Online #3 提高组]优秀子序列

写的 \(O(3^{\log_2 \max\{a_i\}})\) 的做法,原因是不会题解里的自己子集卷积。

首先容易想到一个 \(O (2^{\log_n \max \{ a_i \}} n)\) 的做法,不再赘述。

然后可以发现其实这玩意是位置无关的。

那我们直接开个桶,利用组合数和补集算一下发现 \(f_{i} = f_{j} \cdot t_{i \bigoplus j}\) 其中 \(j\)\(i\) 的子集, \(i \bigoplus j\)\(j\) 的补集。

可以发现 \(f_{i} \cdot t_{i \bigoplus j}\)\(f_{i \bigoplus j} \cdot t_i\) 是同一种情况。

那么我们只考虑 \(i \lt i \bigoplus j\) 的情况。

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const ll MAXN = 1e6+10, MOD = 1e9+7;

ll f[MAXN], N, B = 1, val[MAXN], cnt, prime[MAXN], vis[MAXN], phi[MAXN], ans, t[MAXN], maxn;

int main() {
    scanf("%lld", &N);
    phi[1] = 1, f[0] = 1;
    for (ll i = 2; i <= MAXN - 10; i++) {
        if (!vis[i]) prime[++cnt] = i, phi[i] = i-1;
        for (ll j = 1; j <= cnt && prime[j] * i <= MAXN - 10; j++) {
            vis[prime[j] * i] = 1;
            if (i % prime[j]) {
                phi[i * prime[j]] = phi[i] * phi[prime[j]];
            } else {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
    for (ll i = 1; i <= N; i++) 
        scanf("%lld", val+i), t[val[i]]++, maxn = max(maxn, val[i]);
    while (maxn >= B) B *= 2;
    for (ll i = 1; i <= B; i++) {
    	for (ll j = i;;j = (j - 1) & i) {
			ll s = i ^ j;
			if (j < s) break;
			(f[i] += (f[s] * t[j] % MOD)) %= MOD;
		}
    }
    ans = 1;
    for (ll i = 1; i <= B; i++)
    	(ans += (phi[i+1] * f[i]) % MOD) %= MOD;
    for (ll i = 1; i <= t[0]; i++) 
    	ans = ans * 2 % MOD;
    printf("%lld\n", ans % MOD);
    return 0;
}
posted @ 2020-10-29 21:15  Gensokyo_Alice  阅读(78)  评论(0编辑  收藏  举报