NOI 2015 寿司晚宴 状压DP

题目链接点我点我:-)
题目

为了庆祝 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值