1. 程式人生 > 實用技巧 >P6570 [NOI Online #3 提高組]優秀子序列

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;
}