1. 程式人生 > 其它 >cf577 B. Modulo Sum(抽屜原理/bitset優化01揹包)

cf577 B. Modulo Sum(抽屜原理/bitset優化01揹包)

題意:

給定n個數,問能否選出若干個數的,和為m的倍數

\(1\le n \le 1e6, 2\le m \le 1e3\)

思路:

法一:(取模的性質、抽屜原理、01揹包)

對原陣列做字首和並取模,可能的字首和只有 \([0,m-1]\),m種值。由抽屜原理,若 \(n\ge m\) 就一定有兩個字首和相等,所以一定可以找到和為m的倍數的子段。否則做一下普通的01揹包

const int N = 1e6 + 5, M = 1e3 + 5;
int n, m;
bool f[2][M]; //滾動
main()
{
    cin >> n >> m;

    if(n > m) return cout << "YES", 0;

    for(int i = 1; i <= n; i++)
    {
        int x; cin >> x; x %= m;

        for(int j = 0; j < m; j++)
            if(f[(i-1)&1][j])
                f[i&1][j] = f[i&1][(j+x)%m] = 1;

        f[i&1][x] = 1;
    }

    cout << (f[n&1][0] ? "YES" : "NO");
}

法二:(bitset優化01揹包)

如果沒發現上面的性質,直接做01揹包複雜度 \(O(nm)\) 會超時,bitset優化一下即可。複雜度少一個常數?

開個bitset表示能湊出來的數的集合。注意bitset的最右邊是第0位,最左邊是最高位。

對於某個數 x,可以只選x,或者把前面的方案全部加上x(即左移x位)。加x之後超過m的數要減小m,相當於左移x位再右移m位,即右移 m-x 位。

最後判斷 f[n][0] 是否為1

const int N = 1e6 + 5, M = 1e3 + 5;
int n, m;
bitset<M> f[2]; //滾動
main()
{
    iofast;
    cin >> n >> m;
    if(n > m) return cout << "YES", 0;
    for(int i = 1; i <= n; i++)
    {
        int x; cin >> x; x %= m;
        f[i&1] = f[(i-1)&1] | (f[(i-1)&1] << x) | (f[(i-1)&1] >> (m-x));
        f[i&1][x] = 1; //只選自己
    }

    cout << (f[n&1][0] ? "YES" : "NO");
}