洛谷 P2150 [NOI2015] 寿司晚宴

P2150 [NOI2015] 寿司晚宴

洛谷 P2150 [NOI2015] 寿司晚宴_第1张图片

约定: n ≤ 500 n \leq 500 n500

题意

给定 2 → n 2 \rightarrow n 2n n − 1 n-1 n1 个数字,现在两个人想分别取一些数字(不一定全取完),如果他们两人取的数字中存在: x ∈ A , y ∈ B x \in A,y \in B xA,yB,但 x x x y y y 不互质,那么这种方案不和谐

给出和谐方案的总数

思路

两个人取的数字要互相互质,那么对于 ∀ x ∈ A , y ∈ B \forall x \in A, y \in B xA,yB,有 x x x y y y 一定没有相同的质因子,而 n ≤ 500 n \leq 500 n500 的质数有限,我们可以这样设计: d p [ i ] [ S 1 ] [ S 2 ] dp[i][S_1][S_2] dp[i][S1][S2] 为处理完第 i i i 个数字后,第一个集合拥有的质因子状态为 S 1 S_1 S1,第二个集合拥有的质因子状态为 S 2 S_2 S2 的方案数。

可是小于 500 500 500 的质数太多,我们进一步观察发现:一个数 x x x 最多只有一个大于 x \sqrt x x 的质因子,我们不妨单独处理这个质因子,称其为大质数,而小于 n = 22 \sqrt n = 22 n =22 的质数只有 8 8 8 个: 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 {2, 3, 5, 7, 11, 13, 17, 19} 2,3,5,7,11,13,17,19

所以我们 D P DP DP 的数组的后面集合状态只需要 1 < < 8 1 << 8 1<<8 大小即可。

用结构体来存每个数 x x x 的信息:大质数 和 小质数集合 S S S。对于拥有相同大质数的一类数 x 1 , x 2 , x 3 . . . x_1, x_2, x_3 ... x1,x2,x3...,如果它们要被选择的话,它们一定是要么全部被 A A A 选,要么全部被 B B B 选,否则两个集合会拥有相同的大质数,会矛盾。

我们可以定义两个数组: d p 1 、 d p 2 dp1、dp2 dp1dp2 分别表示全部放入 A A A 和全部放入 B B B 的方案数。后面在大质数即将变化的时候,累加两个数组的答案然后容斥一下一开始的答案即可。

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

struct Num{
    int S; //小质数集合
    int P; //大质数
}a[505];

int D[8] = {2, 3, 5, 7, 11, 13, 17, 19};
ll dp[1 << 8][1 << 8];
ll dp1[1 << 8][1 << 8];
ll dp2[1 << 8][1 << 8];
ll mod;

void init(int x){
    int val = x;
    fore(i, 0, 8){
        int d = D[i];
        if(val % d == 0){
            a[x].S |= 1 << i;
            while(val % d == 0) val /= d;
        }
    }
    if(val != 1) a[x].P = val; //大质数
}

void add(ll& a, ll b){
    a += b;
    if(a >= mod) a -= mod;
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n;
    std::cin >> n >> mod;
    fore(i, 2, n + 1) init(i);
    std::sort(a + 2, a + n + 1, [](const Num& a, const Num& b){
        return a.P < b.P;
    });
    
    dp[0][0] = 1;
    fore(i, 2, n + 1){
        if(a[i].P != a[i - 1].P || !a[i].P){ //大质数变化或开头
            memcpy(dp1, dp, sizeof(dp));
            memcpy(dp2, dp, sizeof(dp));
        }
        for(int s1 = 255; s1 >= 0; --s1)
            for(int s2 = 255; s2 >= 0; --s2){
                if(s2 & s1) continue;
                if((a[i].S & s2) == 0) add(dp1[s1 | a[i].S][s2], dp1[s1][s2]);
                if((a[i].S & s1) == 0) add(dp2[s1][s2 | a[i].S], dp2[s1][s2]);
            }
        if(a[i].P != a[i + 1].P || !a[i].P || i == n) //大质数即将变化或结尾
            for(int s1 = 255; s1 >= 0; --s1)
                for(int s2 = 255; s2 >= 0; --s2){
                    if(s1 & s2) continue;
                    dp[s1][s2] = (dp1[s1][s2] + dp2[s1][s2] - dp[s1][s2] + mod) % mod;
                } //这里要容斥减去一开始复制dp数组的答案
    }

    ll ans = 0;
    for(int s1 = 255; s1 >= 0; --s1)
        for(int s2 = 255; s2 >= 0; --s2){
            if(s1 & s2) continue;
            add(ans, dp[s1][s2]);
        }

    std::cout << ans;
    return 0;
}

你可能感兴趣的:(算法,c++,动态规划)