「NOI2015」寿司晚宴 - 状压 DP

给定 ~ 这 个数,让两个人分别选出一些数,使得对于第一个人选择的任意一个数 和第二个人选择的任意一个数 ,有 ,求方案数。

链接

BZOJ 4197
UOJ #129

题解

因为题目要求的条件不互质就是指没有相同的质因子,一个显然的思路是求出这些数的所有质因子,用 (、 为二进制集合)表示第一个人选择的质因子集合为 、第二个人为 的方案数。

但是这些数的质因子太多,无法进行状压,如果我们不考虑每个数 的(最多一个的)大于 的质因子,则质因子只有 个 —— ,将这些质因子状压。

考虑另一个质因子的影响,设它为 ,则如果一个人选了一个含有质因子 的数,另一个人不能选取任何含有质因子 的数。

将所有数按照 分组,对于每一组,以二进制集合的方式储存其所有数的其它质因子,设第 个数为 。一组数中,最多由某一个人选若干个。

设 表示不选当前组的情况下,第一个人所选集合为 ,第二个人所选集合为 的方案总数。

对于每一个集合,设 表示组内前 个数,全部由第 个人来选,第一个人所选集合为 ,第二个人所选集合为 的方案总数。

转移时,枚举组内每个数 ,分别以 和 更新 和 。

注意到枚举 和 时,所枚举到的 或 均为 或 的字集,体现在十进制意义下,即 、。这启发我们可以像背包一样,滚动掉第一维 ,从大到小枚举 和 ,同时刷表更新 和 。

最后一个问题,对于每个没有大于 的因子的数 ,将这些数每个单独分一组即可。

代码

#include <cstdio>
#include <cstring>
#include <utility>
#include <algorithm>
#include <vector>

const int MAXN = 500;
const int MAXK = 8;
const int MAXSTAT = 1 << 8;
const int PRIMES[] = { 2, 3, 5, 7, 11, 13, 17, 19 };

int main() {
    int n, p;
    scanf("%d %d", &n, &p);

    std::vector< std::vector<int> > v;
    std::vector< std::pair<int, int> > tmp;
    for (int i = 2; i <= n; i++) {
        int x = i, t = 0;
        for (int j = 0; j < 8; j++) {
            if (x % PRIMES[j] == 0) {
                while (x % PRIMES[j] == 0) x /= PRIMES[j];
                t |= 1 << j;
            }
        }

        tmp.push_back(std::make_pair(x, t));
    }

    std::sort(tmp.begin(), tmp.end());

    for (std::vector< std::pair<int, int> >::const_iterator it = tmp.begin(); it != tmp.end(); it++) {
        if (it == tmp.begin() || it->first == 1 || it->first != (it - 1)->first) {
            v.resize(v.size() + 1);
        }
        v.back().push_back(it->second);
    }

    static int f[MAXSTAT][MAXSTAT], g[2][MAXSTAT][MAXSTAT];
    f[0][0] = 1;
    for (std::vector< std::vector<int> >::const_iterator it = v.begin(); it != v.end(); it++) {
        memcpy(g[0], f, sizeof(f));
        memcpy(g[1], f, sizeof(f));

        // puts("new");
        for (std::vector<int>::const_iterator it2 = it->begin(); it2 != it->end(); it2++) {
            // printf("%d\n", *it2);
            for (int a = MAXSTAT - 1; a >= 0; a--) {
                for (int b = MAXSTAT - 1; b >= 0; b--) {
                    // printf("%d %d\n", a, b);
                    if (!(b & *it2)) /* printf("[0] += %d\n", g[0][a][b]), */ (g[0][a | (*it2)][b] += g[0][a][b]) %= p;
                    if (!(a & *it2)) /* printf("[1] += %d\n", g[1][a][b]), */ (g[1][a][b | (*it2)] += g[1][a][b]) %= p;
                    // printf("%d\n", *it2);
                    // printf("%d %d\n", g[0][a][b], g[1][a][b]);
                }
            }
        }

        for (int a = MAXSTAT - 1; a >= 0; a--) {
            for (int b = MAXSTAT - 1; b >= 0; b--) {
                f[a][b] = ((g[0][a][b] + g[1][a][b] - f[a][b]) % p + p) % p;
            }
        }
    }

    int ans = 0;
    for (int a = MAXSTAT - 1; a >= 0; a--) {
        for (int b = MAXSTAT - 1; b >= 0; b--) {
            if (!(a & b)) (ans += f[a][b]) %= p;
        }
    }

    printf("%d\n", ans);

    return 0;
}