为了庆祝NOI的成功开幕,主办方为大家准备了一场寿司晚宴。小G和小W作为参加NOI的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,⋯,n−1 ,其中第种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n )。
现在小G和小W希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小G品尝的寿司种类中存在一种美味度为x的寿司,小W品尝的寿司中存在一种美味度为y的寿司,而x与y不互质。
现在小G和小W希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数p取模)。注意一个人可以不吃任何寿司。
题外话:
上午听某大神讲DP,讲了不少NOI的好(恶心)题(然而基本没有听懂)。结果晚上水NOI2015时就碰到了。
一开始偷瞄了题目标签,看到状压后很容易想到压质因子,不过看了看顶多拿一半分。感觉应该是 O(n∗2n√) 的做法,但细节想不清楚。。
思路:
果然只要压 n−−√ 以内的质因子,对所有数按其大于 n−−√ 的因数分组,若没有大于 n−−√ 的因数则单独分组。对于小于 n−−√ 的因子实际上有3种状态,即某一个人选或者都不选,但这样不是很方便,简单起见我们用 f[i][j] 表示第一个人取的状态为 i ,第二个人取的状态为 j 的方案数。然后一开始就去掉无效状态加以优化(其实不优化也行)。
然后对于每一组数,显然某个人只要选了其中的一个,另外一个人就一个也不能选,于是我们每次将f复制到 dp[][] ,转移也就不难推了。处理完一组后再令 f[u][v]=dp[u][v]+dp[v][u]−f[i][j] ,表示某一个人选或不选这一组数,由于这两种情况都包含了两人都不选的情况,所以还需要减掉重复
由于小于 n−−√ 的质数最多只有8个,所以复杂度为 O(n∗38) 。
代码:
#include
#include
#include
#define For(i,j,k) for(int i = j;i <= k;i++)
#define Forr(i,j,k) for(int i = j;i >= k;i--)
const int N = 256;
const int Prime[] = {1, 2, 3, 5, 7, 11, 13, 17, 19};
using namespace std;
struct Num{
int fac, s;
bool operator < (const Num& A) const{
return fac < A.fac;
}
}A[N<<1];
typedef long long LL;
LL f[N][N], dp[N][N], Mod;
int n, Begin[N], Next[N * N], to[N * N], e;
int main(){
scanf("%d%lld", &n, &Mod);
For(i,1,n-1){
int t = i + 1;
For(j,1,8) while(t % Prime[j] == 0)
t /= Prime[j], A[i].s |= 1 << (j - 1);
A[i].fac = t;
}
For(u,0,255)
For(v,0,255)
if(!(u & v)){
Next[++e] = Begin[u];
Begin[u] = e;
to[e] = v;
}
sort(A + 1, A + n);
f[0][0] = dp[0][0] = 1;
int l = 1;
while(l < n){
int r = l;
while(A[r+1].fac == A[l].fac && A[l].fac != 1) ++r;
For(i,l,r)
Forr(u,255,0) for(int j = Begin[u];j;j = Next[j]){
int v = to[j];
if(!(A[i].s & u)) dp[u][v|A[i].s] =
(dp[u][v|A[i].s] + dp[u][v]) % Mod;
}
For(u,0,255)
for(int j = Begin[u];j;j = Next[j]){
int v = to[j];
f[u][v] =
(dp[u][v] + dp[v][u] - f[u][v] + Mod) % Mod;
}
For(u,0,255)
for(int j = Begin[u];j;j = Next[j])
dp[u][to[j]] = f[u][to[j]];
l = r + 1;
}
LL Ans = 0;
For(u,0,255) For(v,0,255) Ans = (Ans + f[u][v]) % Mod;
printf("%lld\n", Ans);
return 0;
}