跳转至

SOSDP

例题

G-A Xor B Problem again

已知数组 \(a\), 求 \(a_i\&a_j=0\) 的个数.

\(a\) 按位取反, 使用 SOSDP 统计某二进制的超集和.

答案为 \(\sum(f[a_i])\).

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
const int B = 20;
const int N = 1 << B;
const int M = N - 1;
i64 f[N];
int a[100005];
int main() {
    int n;
    cin >> n;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
        f[M & ~a[i]]++;
    }
    for (int i = 0 ; i < B ; i++) {
        for (int mask = 0 ; mask <= M ; mask++) {
            if (mask & 1LL << i) {
                f[mask ^ 1LL << i] += f[mask];
            }
        }
    }
    i64 ans = 0;
    for (int i = 1 ; i <= n ; i++) {
        ans += f[a[i]];
    }
    cout << ans << "\n";
    return 0;
}

Problem - E - Codeforces

已知数组 \(a\), \(a_i\&a_j=0\)\(a[j]\).

可以对所有 \(~a[i]\) 做 SOSDP, 预处理二进制对应位置为1一定是 \(a[i]\) 的子集.

最后 \(f[a[i]]\)\(a[i]\) 的答案.

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
const int B = 22;
const int N = 1 << B;
const int M = N - 1;
i64 f[N];
int a[1000005];
int main() {
    int n;
    cin >> n;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
        f[M & ~a[i]] = a[i];
    }
    for (int i = 0 ; i < B ; i++) {
        for (int mask = 0 ; mask <= M ; mask++) {
            if (mask & 1LL << i) {
                if (f[mask] != 0) {
                    f[mask ^ 1LL << i] = f[mask];
                }
            }
        }
    }
    for (int i = 1 ; i <= n ; i++) {
        if (f[a[i]] == 0) f[a[i]] = -1;
        cout << f[a[i]] << " ";
    }
    return 0;
}
习题

E - Or Plus Max

已知数组 \(a\), \(i|j≤k\)\(max(a_i+a_j)\).

枚举 \(k\), SOSDP 处理 \(k\) 的二进制子集中最大值和次大值.

最后循环输出最大值与次大值之和即可.

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
const int D = 20;
const int N = 1 << D;
const int M = N - 1;
array<int, 2> f[N];
int main() {
    int n;
    cin >> n;
    n = 1 << n;
    for (int i = 0 ; i < n ; i++) {
        cin >> f[i][0];
        f[i][1] = 0x7fffffff + 1;
    }
    for (int i = 0 ; i < D ; i++) {
        for (int mask = 0 ; mask < n ; mask++) {
            if (mask & 1LL << i) {
                auto x = f[mask];
                auto y = f[mask ^ 1LL << i];
                if (x < y) swap(x, y);
                x[1] = max(x[1], y[0]);
                f[mask] = x;
            }
        }
    }
    int ans = 0;
    for (int i = 1 ; i < n ; i++) {
        ans = max(ans, f[i][0] + f[i][1]);
        cout << ans << "\n";
    }
    return 0;
}

Problem - F - Codeforces

已知 \(i<j<k\)\(max(a_i|(a_j\&a_k))\)

\(f_{i,j}\) 表示为 二进制为 \(i\) 的第 \(j\) 小的下标, 因为这里只有 \(a_j\&a_k\) 所以只需要最小的 \(2\) 个值.

然后做子集前缀和, 最后从小到大枚举 \(i\), 再大到小枚举 \(a_i\) 二进制为 \(0\) 的位, 检查是否存在 \(a_j\&a_k\) 的这一位为1.

答案取 \(max(a_i|(a_j\&a_k))\)

代码
C++
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int __OneWan_2024 = [](){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    return 0;
}();
const int B = 21;
const int N = 1 << B;
const int M = N - 1;
array<int, 2> f[N];
int a[N];
void add(int x, int val) {
    if (val > f[x][0]) {
        swap(val, f[x][0]);
    }
    if (val > f[x][1]) {
        swap(val, f[x][1]);
    }
}
int main() {
    int n;
    cin >> n;
    fill(f, f + N + 1, array<int, 2>{-1, -1});
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
        add(a[i], i);
    }
    for (int i = 0 ; i < B ; i++) {
        for (int mask = 0 ; mask <= M ; mask++) {
            if (mask & 1LL << i) {
                add(mask ^ 1LL << i, f[mask][0]);
                add(mask ^ 1LL << i, f[mask][1]);
            }
        }
    }
    int ans = 0;
    for (int i = 1 ; i <= n - 2 ; i++) {
        int res = 0;
        for (int j = B - 1 ; j >= 0 ; j--) {
            if (a[i] & 1LL << j) continue;
            if (f[res ^ 1LL << j][1] > i) {
                res ^= 1LL << j;
            }
            ans = max(ans, res | a[i]);
        }
    }
    cout << ans << "\n";
    return 0;
}

F - Subsequence LCM (atcoder.jp)

\(n\) 个数(\(1\leq a_i\leq10^{16}\)), 问有多少个子序列满足 \(lcm=m\)

先考虑 \(m=1\),此时答案为 \(2^k-1\)\(k\)\(a_i=1\) 的个数。

然后 \(m>1\)\(10^{16}\) 最多是 \(15\) 个不同的质数的乘积。

所以我们可以考虑处理 \(a_i\)

如果 \(cnt_j(a_i)=cnt_j(m)\),则二进制第 \(j\) 位为1。

如果 \(cnt_j(a_i)>cnt_j(m)\),则 \(a_i\) 没有贡献。

如果 \(cnt_j(a_i)<cnt_j(m)\),则说明可选可不选,二进制第 \(j\) 位为0。

通过 SOSDP 可以求得子集前缀和,然后通过子集反演,容斥出恰好每个质因数都满足条件的个数。

代码
C++
using i64 = long long;
const i64 P = 998244353;
i64 qpow(i64 a, i64 b = P - 2, i64 res = 1) {
    while (b) {
        if (b & 1) res = res * a % P;
        a = a * a % P;
        b >>= 1;
    }
    return res;
}
i64 dp[1 << 16];
int main() {
    int n;
    cin >> n;
    i64 m;
    cin >> m;
    vector<i64> a;
    vector<pair<i64, int>> prime;
    i64 temp = m;
    for (i64 i = 2 ; i * i <= temp ; i++) {
        if (temp % i == 0) {
            int cnt = 0;
            while (temp % i == 0) {
                temp /= i;
                cnt++;
            }
            prime.push_back({i, cnt});
        }
    }
    if (temp > 1) {
        prime.push_back({temp, 1});
    }
    for (int i = 0 ; i < n ; i++) {
        i64 a;
        cin >> a;
        if (m % a != 0) continue;
        int res = 0;
        bool flag = true;
        for (int j = 0 ; j < (int) prime.size() ; j++) {
            int cnt = 0;
            while (a %  prime[j].first == 0) {
                a /= prime[j].first;
                cnt++;
            }
            if (cnt > prime[j].second) {
                flag = false;
                break;
            }
            if (cnt == prime[j].second) {
                res |= 1 << j;

            }
        }
        if (flag && a == 1) {
            dp[res]++;
        }
    }
    if (m == 1LL) {
        cout << (qpow(2, dp[0]) - 1 + P) % P << "\n";
        return 0;
    }
    int k = prime.size();
    for (int i = 0 ; i < k ; i++) {
        for (int mask = 0 ; mask < 1 << k ; mask++) {
            if (mask & 1 << i) {
                dp[mask] = (dp[mask ^ 1 << i] + dp[mask]) % P;
            }
        }
    }
    i64 ans = 0;
    for (int i = 0 ; i < 1 << k ; i++) {
        dp[i] = qpow(2, dp[i]);
        ans = (ans + ((k - __builtin_popcount(i)) & 1 ? P - 1 : 1) * dp[i] % P) % P;
    }
    cout << ans;
    return 0;
}