P2150 [NOI2015] 寿司晚宴
约定: n ≤ 500 n \leq 500 n≤500
题意
给定 2 → n 2 \rightarrow n 2→n 共 n − 1 n-1 n−1 个数字,现在两个人想分别取一些数字(不一定全取完),如果他们两人取的数字中存在: x ∈ A , y ∈ B x \in A,y \in B x∈A,y∈B,但 x x x 与 y y y 不互质,那么这种方案不和谐
给出和谐方案的总数
思路
两个人取的数字要互相互质,那么对于 ∀ x ∈ A , y ∈ B \forall x \in A, y \in B ∀x∈A,y∈B,有 x x x 和 y y y 一定没有相同的质因子,而 n ≤ 500 n \leq 500 n≤500 的质数有限,我们可以这样设计: 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 dp1、dp2 分别表示全部放入 A A A 和全部放入 B B B 的方案数。后面在大质数即将变化的时候,累加两个数组的答案然后容斥一下一开始的答案即可。
#include<bits/stdc++.h>
#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;
}