为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n)。
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。
输入文件的第 1 行包含 2 个正整数 n,p,中间用单个空格隔开,表示共有 n 种寿司,最终和谐的方案数要对 p 取模。
输出一行包含 1 个整数,表示所求的方案模 p 的结果。
3 10000
9
2≤n≤500
0< p≤1000000000
题意:求满足以下条件的集合对(A,B)的数量:
(1) A⊆[2,n], B ⊆[2,n] (2) A∩B=∅ (3) ∀x∈A, y∈B, gcd(x,y)=1
首先看30分。
2~30的质数至多有10个,而两个集合不能选相同的质数,所以可以状压一下:
dp[i][x][y]表示选到第i个数,第一个人选的素数状态是x,第二个人选的素数状态是y的方案数,每次像01背包来转移就能优化掉第一维,然后ans累加就行了。
然后再看100分:
小于 n−−√ 的质数至多有八个,所以可以状压一下。对于一个数n,大于 n−−√ 的质数至多有1个,所以可以顺序DP。
把大质数相同的数放到一个集合里(排一下序),用dp[x][y][0/1]表示第一个人状态是x,第二个人状态是y,大质数分给第一个人/第二个人的方案数。然后状压那里还是像30分算法那样转移状态,唯一的区别就是加个第三维…
f[x][y] 表示第一个人x,第二个人y,选了前i个大质数的方案数。i可以01背包那样优化掉。
然后每次当前大质数的集合开始时,把f复制一份给dp数组让他转移。
结束时, f[x][y]=dp[x][y][0]+dp[x][y][1]−f[x][y] 累加。
这是30分:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int SZ = 5010;
const int MAXC = (1 << 11) - 1;
int pri[233] = {0,2,3,5,7,11,13,17,19,23,29}; //10
int n,mod;
int a[SZ],g[SZ],dp[SZ][SZ];
void print(int x)
{
for(int i = 10;i >= 0;i --)
printf("%d",x >> i & 1);
printf(" ");
}
void calc(int x)
{
int xx = x;
for(int i = 1;i <= 10;i ++)
{
int v = pri[i];
if(x % v == 0)
{
while(x % v == 0) x /= v;
a[xx] |= (1 << i);
}
}
}
int main()
{
scanf("%d%d",&n,&mod);
for(int i = 1;i <= n;i ++)
calc(i);
// for(int i = 0;i <= MAXC;i ++)
// print(i),printf("%d\n",g[i]);
dp[0][0] = 1;
for(int i = 2;i <= n;i ++)
{
for(int j = MAXC;j >= 0;j --)
{
for(int k = MAXC;k >= 0;k --)
{
if((a[i] & k) == 0) dp[j | a[i]][k] = (dp[j | a[i]][k] + dp[j][k]) % mod;
if((a[i] & j) == 0) dp[j][k | a[i]] = (dp[j][k | a[i]] + dp[j][k]) % mod;
}
}
}
int ans = 0;
for(int i = MAXC;i >= 0;i --)
{
for(int j = MAXC;j >= 0;j --)
{
if((i & j) == 0)
{
// if(dp[i][j])
// print(i),print(j),printf("%d\n",dp[i][j]);
ans = (ans + dp[i][j]) % mod;
}
}
}
printf("%d",ans);
return 0;
}
100分:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const int SZ = 5010;
const int MAXC = 1 << 8;
int pri[233] = {2,3,5,7,11,13,17,19}; //10
int n,mod;
struct haha{
int a,p;
}l[SZ];
bool operator <(haha a,haha b)
{
return a.p < b.p;
}
int dp[SZ][SZ][2];
int f[SZ][SZ];
void print(int x)
{
for(int i = 10;i >= 0;i --)
printf("%d",x >> i & 1);
printf(" ");
}
void calc(int x)
{
int xx = x;
for(int i = 0;i < 8;i ++)
{
int v = pri[i];
if(x % v == 0)
{
while(x % v == 0) x /= v;
l[xx].a |= (1 << i);
}
}
l[xx].p = x;
}
int main()
{
scanf("%d%d",&n,&mod);
for(int i = 2;i <= n;i ++)
calc(i);
// for(int i = 0;i <= MAXC;i ++)
// print(i),printf("%d\n",g[i]);
sort(l + 2,l + n + 1);
f[0][0] = 1;
for(int i = 2;i <= n;i ++)
{
if(i == 2 || l[i].p == 1 || l[i].p != l[i - 1].p)
for(int j = MAXC;j >= 0;j --)
for(int k = MAXC;k >= 0;k --)
dp[j][k][0] = dp[j][k][1] = f[j][k];
for(int j = MAXC;j >= 0;j --)
{
for(int k = MAXC;k >= 0;k --)
{
if((l[i].a & k) == 0)
dp[j | l[i].a][k][0] = (dp[j | l[i].a][k][0] + dp[j][k][0]) % mod;
if((l[i].a & j) == 0)
dp[j][k | l[i].a][1] = (dp[j][k | l[i].a][1] + dp[j][k][1]) % mod;
}
}
if(i == n || l[i].p == 1 || l[i].p != l[i + 1].p)
for(int j = MAXC;j >= 0;j --)
for(int k = MAXC;k >= 0;k --)
f[j][k] = ((LL)dp[j][k][0] + dp[j][k][1] - f[j][k]) % mod;
}
int ans = 0;
for(int i = MAXC;i >= 0;i --)
{
for(int j = MAXC;j >= 0;j --)
{
if((i & j) == 0)
{
ans = (ans + f[i][j]) % mod;
}
}
}
printf("%d",(ans + mod) % mod);
return 0;
}
/* 30 30 */