cf577 B. Modulo Sum(抽屉原理/bitset优化01背包)

题意:

给定n个数,问能否选出若干个数,它们的和是m的倍数

\(1\le n \le 1e6, 2\le m \le 1e3,0\le a_i\le 1e9\)

思路:

法一:(取模的性质、抽屉原理、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

//468ms
const int N = 1e6 + 5, M = 1e3 + 5;
int n, m; bitset<M> f;
void sol() {
    cin >> n >> m;
    while(n--) {
        int x; cin >> x; x %= m;
        f = f | (f << x) | (f >> (m-x));
        f[x] = 1; //只选自己
    }
    cout << (f[0] ? "YES" : "NO");
}

posted @ 2022-03-01 16:29  Bellala  阅读(124)  评论(0)    收藏  举报