「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;
}