我们考虑一类一类地进行动态规划。设
fi,j,S1,S2
f
i
,
j
,
S
1
,
S
2
表示考虑了大质因子小于
i
i
的所有数以及大质因子等于 i 的前
j
j
个数时,A选的小质因子集合为 S1,B 为
S2
S
2
时的方案数(一群神仙,带有滚动数组的状态从来不需要补充完整,直接设,真的是太强啦!我只有把整个状态写出来才看得懂,我真是太弱啦!)。显然对于
i=1
i
=
1
的情况做法与前面部分相同。
我们考虑
i>1
i
>
1
的情况。设
gi,j,S1,S2,0/1
g
i
,
j
,
S
1
,
S
2
,
0
/
1
表示考虑了大质因子小于
i
i
的所有数以及大质因子等于 i 的前
j
j
个数,且大质因子等于 i 的数由 A/B 来取(可以选择不取),第一个人选的小质因子集合为
S1
S
1
,第二个人为
S2
S
2
时的方案数。前面已经说了,对于大质因子大于
1
1
的数最多只能有一个人去取,所以这么设状态是明确的。
显然边界条件为 gi,0,S1,S2,0/1=fi−1,all,S1,S2(都还没有开始选,当然啦)。
g
g
的转移和之前的 f 很相似,只不过只能有其中一个人进行选择。我们同样使用刷表法:
gi,j+1,S1,S2,0 += gi,j,S1,S2,0
g
i
,
j
+
1
,
S
1
,
S
2
,
0
+=
g
i
,
j
,
S
1
,
S
2
,
0
gi,j+1,S1∪maski,j+1,S2,0 += gi,j,S1,S2,0(S2∩maski,j+1=∅)
g
i
,
j
+
1
,
S
1
∪
m
a
s
k
i
,
j
+
1
,
S
2
,
0
+=
g
i
,
j
,
S
1
,
S
2
,
0
(
S
2
∩
m
a
s
k
i
,
j
+
1
=
∅
)
gi,j+1,S1,S2,1 += gi,j,S1,S2,1
g
i
,
j
+
1
,
S
1
,
S
2
,
1
+=
g
i
,
j
,
S
1
,
S
2
,
1
gi,j+1,S1,S2∪maski,j+1,1 += gi,j,S1,S2,1(S1∩maski,j+1=∅)
g
i
,
j
+
1
,
S
1
,
S
2
∪
m
a
s
k
i
,
j
+
1
,
1
+=
g
i
,
j
,
S
1
,
S
2
,
1
(
S
1
∩
m
a
s
k
i
,
j
+
1
=
∅
)
我们又有:
fi,all,S1,S2=gi,all,S1,S2,0+gi,all,S1,S2,1−fi,0,S1,S2
f
i
,
a
l
l
,
S
1
,
S
2
=
g
i
,
a
l
l
,
S
1
,
S
2
,
0
+
g
i
,
a
l
l
,
S
1
,
S
2
,
1
−
f
i
,
0
,
S
1
,
S
2
两个
g
g
相加很显然:要么 A 来,要么
B
B
来,满足加法原理。减去右式的原因是 A 和 B 都可以不选以 i 为大质因子的数,所以这样这种情况就算了两次。显然这种情况的方案数就是
fi,0,S1,S2
f
i
,
0
,
S
1
,
S
2
,所以减去它就可以了。
然后我们进行滚动数组优化,发现跟前面一样可以不保存
i
i
和 j,只需要倒着枚举集合就可以了。这样就有了状态转移方程的形式就和绝大部分题解一样了(唉,神仙的题解我根本看不懂,我太弱啦!)。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
#define loop register int
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
}
const int prime[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
LL mod;
int n;
#define RunInstance(x) delete new x
struct brute
{
int mask[35];
LL f[1 << 10][1 << 10];
brute() : mask(), f()
{
for (int i = 2; i <= n; i++)
for (int j = 0; j < 10; j++)
if (i % prime[j] == 0)
mask[i] |= 1 << j;
int U = 1 << 10;
f[0][0] = 1;
for (loop i = 2; i <= n; i++)
{
loop m = mask[i];
for (loop S1 = U - 1; ~S1; S1--)
for (loop S2 = U - 1; ~S2; S2--) if (f[S1][S2])
{
if (!(S2 & m))
(f[S1 | m][S2] += f[S1][S2]) %= mod;
if (!(S1 & m))
(f[S1][S2 | m] += f[S1][S2]) %= mod;
}
}
LL ans = 0;
for (loop S1 = 0; S1 < U; S1++)
for (loop S2 = 0; S2 < U; S2++)
(ans += f[S1][S2]) %= mod;
printOut(ans);
}
};
struct work
{
struct Number
{
int num;
int mask;
int big;
Number() {}
Number(int num, int mask, int big) : num(num), mask(mask), big(big) {}
bool operator<(const Number& b) const { return big < b.big; }
};
std::vector<Number> number[505];
LL f[1 << 8][1 << 8];
LL g[2][1 << 8][1 << 8];
work() : f()
{
for (int i = 2; i <= n; i++)
{
int t = i;
int mask = 0;
for (int j = 0; j < 8; j++)
{
if (t % prime[j]) continue;
mask |= 1 << j;
while (!(t % prime[j]))
t /= prime[j];
}
number[t].push_back(Number(i, mask, t));
}
int U = 1 << 8;
f[0][0] = 1;
for (int i = 0; i < number[1].size(); i++)
{
loop m = number[1][i].mask;
for (loop S1 = U - 1; ~S1; S1--)
for (loop S2 = U - 1; ~S2; S2--) if (f[S1][S2])
{
if (!(S2 & m))
(f[S1 | m][S2] += f[S1][S2]) %= mod;
if (!(S1 & m))
(f[S1][S2 | m] += f[S1][S2]) %= mod;
}
}
for (int i = 2; i <= n; i++) if (number[i].size())
{
memcpy(g[0], f, sizeof(g[0]));
memcpy(g[1], g, sizeof(g[1]));
for (int j = 0; j < number[i].size(); j++)
{
loop m = number[i][j].mask;
for (loop S1 = U - 1; ~S1; S1--)
for (loop S2 = U - 1; ~S2; S2--) if (g[0][S1][S2])
{
if (!(S2 & m))
(g[0][S1 | m][S2] += g[0][S1][S2]) %= mod;
if (!(S1 & m))
(g[1][S1][S2 | m] += g[1][S1][S2]) %= mod;
}
}
for (loop S1 = U - 1; ~S1; S1--)
for (loop S2 = U - 1; ~S2; S2--)
f[S1][S2] = ((g[0][S1][S2] + g[1][S1][S2] - f[S1][S2]) % mod + mod) % mod;
}
LL ans = 0;
for (loop S1 = 0; S1 < U; S1++)
for (loop S2 = 0; S2 < U; S2++)
(ans += f[S1][S2]) %= mod;
printOut(ans);
}
};
void run()
{
n = readIn();
mod = readIn();
if (n <= 30)
RunInstance(brute);
else
RunInstance(work);
}
int main()
{
run();
return 0;
}