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
复制到 r1
和 r2
,然后对 r1
和 r2
进行转移:
\[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;
}