【每日一题】补档 ARC136D - Without Carry | 子集DP | 困难

题目内容

原题链接

给定一个长度为 n n n 的整数数组 a a a ,问有多少个不同的下标对 i i i j j j 满足 a i + a j a_i+a_j ai+aj 的加法中没有进位。

数据范围

2 ≤ n ≤ 1 0 6 2\leq n\leq 10^6 2n106
0 ≤ a i < 1 0 6 0\leq a_i <10^6 0ai<106

题解

十进制的子集DP

f [ j ] [ i ] f[j][i] f[j][i] 表示前 j j j 位(第1位是个位,第2位是十位)的数 i i i 的子集

  • 考虑前 j − 1 j-1 j1
    f [ j ] [ i ] = f [ j − 1 ] [ i ] f[j][i]=f[j-1][i] f[j][i]=f[j1][i]
  • 考虑第 j j j 位的数 i i i 的子集
    i − 1 0 j − 1 , i − 2 × 1 0 j − 1 , . . . i-10^{j-1}, i-2\times 10^{j-1},... i10j1,i2×10j1,...
    需要考虑的是 i − 1 0 j − 1 i- 10^{j-1} i10j1 的子集也是 i i i 的子集,所以只需要加上 i − 1 0 j − 1 i-10^{j-1} i10j1 的子集即可。
    f [ j ] [ i ] = f [ j ] [ i ] + f [ j ] [ i − 1 0 j − 1 ] f[j][i]=f[j][i]+f[j][i-10^{j-1}] f[j][i]=f[j][i]+f[j][i10j1]

因为每次只用到前一位的状态,所以可以直接滚动数组优化。

时间复杂度: O ( 6 n ) O(6n) O(6n) 6 6 6 是因为值域至多是 6 6 6 位数

代码

#include 
using namespace std;

const int MAX = 1000000;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    vector<int> a(n);
    vector<long long> f(MAX);

    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        f[a[i]] += 1;
    }

    // f[j][i] 表示只考虑十进制的前 j 位 (个位是第一位),i 的子集的数量
    // f[j][i] = f[j - 1][i]
    // 那么只考虑第 j 位
    // 对于第 j 位,i-10^{j-1}, i-2*10^{j-1} ... 都是其子集
    // 但是 f[i-10^{j-1}] 的子集也是 f[i][j] 的子集,所以只考虑这个即可
    // f[j][i] += f[j][i-10^{j-1}]
    for (int j = 1; j < MAX; j *= 10) {
        for (int i = 0; i < MAX; ++i) {
            if (i / j % 10 > 0) {
                f[i] += f[i - j];
            }
        }
    }

    long long ans = 0;
    for (int x: a) {
        ans += f[999999 - x];
        bool ok = true;
        while (x > 0) {
            if (x % 10 > 4) {
                ok = false;
                break;
            }
            x /= 10;
        }
        if (ok) ans -= 1;
    }

    cout << ans / 2 << "\n";

    return 0;
}

你可能感兴趣的:(算法竞赛,DP,算法)