P2150 [NOI2015] 寿司晚宴

传送门

做完了卡牌,便寻思着来加强一下这种套路

结果关键处(根号分治)一样之外,其他做法是完全不同的......


思路

对于 \(n\) 比较小的情况,我们可以考虑进行状压DP

一个集合 \(S\) 存着一些质数,表示选择的数中的质因子含有这些数

我们可以设计一个状态 dp[S1][S2],表示甲选择的数的质因子为 S1,乙选择的数的质因子为 S2,且 \(S1 \cap S2 = \oslash\)

我们考虑每个数进行转移,设 \(i\) 分解质因子的集合为 \(k\),那么就有转移:

\[dp[i][S1|k][S2]+=dp[i-1][S1][S2],\ k \cap S2=\oslash \]

\[dp[i][S1][S2|k]+=dp[i-1][S1][S2],\ k \cap S1=\oslash \]

\(n\) 比较大时,我们就无法全部进行状压来DP

但我们可以观察到,每个数最多只会含有一个大于 \(\sqrt n\) 的质因子

我们先将序列按照最大质因子进行排序,对最大质因子相同的数一起处理

我们令 \(r_1[S1][S2]\) 表示甲选择这种最大质因子的方案数,\(r_2[S1][S2]\) 表示乙选择这种最大质因子的方案数

我们开始先将 dp 复制到 r1r2,然后对 r1r2 进行转移:

\[r_1[S1|k][S2]+=r_1[S1][S2] \]

\[r_2[S1][S2|k]+=r_2[S1][S2] \]

然后我们统计到 dp 的答案就为:

\[dp[S1][S2]=r_1[S1][S2]+r_2[S1][S2]-dp[S1][S2] \]

这里之所以要减去一个 \(dp[S1][S2]\),是因为 \(r_1\)\(r_2\) 都包含了不选这种最大质因数的方案,重复了

最后将所有状态统计答案即可


代码

#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
inline LL reads()
{
    LL sign = 1, re = 0; char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') sign = -1; c = getchar();}
    while('0' <= c && c <= '9'){re = re * 10 + (c - '0'); c = getchar();}
    return sign * re;
}
struct Node
{
    int num, Mx, st;
}a[505];
inline bool cmp(Node a, Node b){return a.Mx < b.Mx;}
LL n, mod, sq;
inline void add(LL &a, LL b) {a + b >= mod ? a += b - mod : a += b;}
inline LL radd(LL a, LL b) {return a + b >= mod ? a + b - mod : a + b;}
inline LL sub(LL a, LL b) {return a - b < 0 ? a - b + mod : a - b;}
int prime[505], pcnt, scnt; std::bitset<505> vis;
inline void Init()
{
    for(int i = 2; i <= n; i++)
    {
        if(!vis[i])
        {
            prime[++pcnt] = i;
            if(i <= sq) scnt++;
        }
        for(int j = 1; j <= pcnt && i * prime[j] <= n; j++)
        {
            int k = i *prime[j];
            vis[k] = true;
            if(!(i % prime[j])) break;
        }
    }
    for(int i = 2; i <= n; i++)
    {
        int rp = i;
        for(int j = 1; j <= scnt; j++)
            if(!(rp % prime[j]))
            {
                a[i].st |= 1 << (j - 1);
                while(!(rp % prime[j])) rp /= prime[j];
            }
        if(rp ^ 1) a[i].Mx = rp;
    }
}
LL dp[520][520], r1[520][520], r2[520][520], ans;
signed main()
{
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    n = reads(), mod = reads(); sq = sqrt(n);
    Init();
    for(int i = 2; i <= n; i++) a[i].num = i;
    std::sort(a + 2, a + 1 + n, cmp);
    dp[0][0] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!a[i].Mx || a[i].Mx != a[i - 1].Mx)
            memcpy(r1, dp, sizeof(dp)), memcpy(r2, dp, sizeof(dp));
        for(int j = (1 << scnt) - 1; j >= 0; j--)
            for(int k = (1 << scnt) - 1; k >= 0; k--)
            {
                if(j & k) continue;
                if(!(a[i].st & k)) add(r1[j | a[i].st][k], r1[j][k]);
                if(!(a[i].st & j)) add(r2[j][k | a[i].st], r2[j][k]);
            }
        if(!a[i].Mx || a[i].Mx != a[i + 1].Mx)
        {
            for(int j = 0; j < (1 << scnt); j++)
                for(int k = 0; k < (1 << scnt); k++)
                {
                    if(j & k) continue;
                    dp[j][k] = sub(radd(r1[j][k], r2[j][k]), dp[j][k]);
                }
        }
    }
    for(int i = 0; i < (1 << scnt); i++)
        for(int j = 0; j < (1 << scnt); j++)
            if(!(i & j)) add(ans, dp[i][j]);
    printf("%lld", ans);
    return 0;
}
posted @ 2022-05-09 21:32  zuytong  阅读(35)  评论(0编辑  收藏  举报