题目链接: 点我点我:-)
题目:
为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 ii 种寿司的美味度为 i+1(即寿司的美味度为从 2 到 n)。
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 pp 取模)。注意一个人可以不吃任何寿司。
输入格式
输入文件的第 1行包含 2 个正整数 n,p,中间用单个空格隔开,表示共有 n 种寿司,最终和谐的方案数要对 pp 取模。
输出格式
输出一行包含 1 个整数,表示所求的方案模 p 的结果。
思路:
先考虑,要全部互质,那么我们的两个人的素因子不能相同,所以想到用素因子作状压DP。但是超过
n√
的素因子,只会出现一次,我们可以把拥有同样超过
n√
的素因子的数字分为一类一起进行处理,在dp的时候一类数只准一个人选。然后剩下的不超过
n√
的素因子只有8个的,状压没问题啦!!!
f[i][j] 表示的是第1个人选了i这个素数集合,第二个人选了j这个素数集合的方案数
p[0/1][i][j]表示的是第1个人选了i这个素数集合,第二个人选了j这个素数集合并且当前这一类超过
n√
的素因子类是由第1/2个人所控制的。
上面提到,然后对于每一类数,某个人只要选了其中的一个,另外一个人就不能选了所以我们可以每次将f复制到p[][],再进行转移
处理完一组再令 f[j][k] = p[0][j][k]+p[1][j][k]-f[j][k]
我们减去东西是因为p加起来以后,包含了两人都不选的情况,所以还要减去重复的部分f[j][k]
感想:
本题在不知道是动规的时候根本不知道怎么做(30分暴力会写???)然后咩,知道是动规以后,并没有什么用啊。于是去查题解,啊还看了好久,最后“抄”对了。又仔细想一想p[0][j][k]应该是等于p[1][k][j]的,嗯嗯应该可以再加速的,并且有非常多的冗余状态,但是懒得动了。
不过段大神把代码进行了很大程度上的优化,包括上述,传送门:点我点我:-)
代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
#define pb push_back
#define Set(a, v) memset(a, v, sizeof(a))
#define For(i, a, b) for(int i = (a); i <= (int)(b); i++)
#define Forr(i, a, b) for(int i = (a); i >= (int)(b); i--)
#define MAXN (500+5)
#define MAXS (255+5)
struct node{
int kind, se;
bool operator <(const node &rhs)const{
if(kind != rhs.kind) return kind < rhs.kind;
return se < rhs.se;
}
};
int f[MAXS][MAXS], p[2][MAXS][MAXS];
int prime[8] = {2, 3, 5, 7, 11, 13, 17, 19};
node num[MAXN];
int main(){
int n, P;
scanf("%d%d", &n, &P);
For(i, 1, n){
int now = i;
For(j, 0, 7)
if(now % prime[j] == 0){
num[i].se |= (1<<j);
while(now%prime[j] == 0) now /= prime[j];
}
num[i].kind = now;
}
sort(num+2, num+n+1);
f[0][0] = p[0][0][0] = p[1][0][0] = 1;
For(i, 2, n){
Forr(j, 255, 0) Forr(k, 255, 0){
if(!(k&num[i].se)) p[0][j|num[i].se][k] = (p[0][j|num[i].se][k]+p[0][j][k])%P;
if(!(j&num[i].se)) p[1][j][k|num[i].se] = (p[1][j][k|num[i].se]+p[1][j][k])%P;
}
if(i==n || num[i].kind==1 || num[i+1].kind!=num[i].kind)
For(j, 0, 255) For(k, 0, 255){
f[j][k] = ((p[0][j][k]+p[1][j][k]-f[j][k])%P+P)%P;
p[0][j][k] = p[1][j][k] = f[j][k];
}
}
int ans = 0;
For(i, 0, 255) For(j, 0, 255) if(!(i&j)) ans = (ans+f[i][j])%P;
printf("%d\n", ans);
return 0;
}