[NOI2015]寿司晚宴(状态压缩动态规划)

题目描述

为了庆祝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取模)。注意一个人可以不吃任何寿司。
输入输出格式
输入格式:

从文件dinner.in中读入数据。

输入文件的第1行包含2个正整数n,p中间用单个空格隔开,表示共有n种寿司,最终和谐的方案数要对p取模。

输出格式:

输出到文件dinner.out中。

输出一行包含1个整数,表示所求的方案模p的结果。

输入输出样例
输入样例#1:

3 10000

输出样例#1:

9

输入样例#2:

4 10000

输出样例#2:

21

输入样例#3:

100 100000000

输出样例#3:

3107203

说明
【数据范围】
这里写图片描述

【时限1s,内存512M】

**

代码
--

**
/*
首先显然只需要考虑不超过 √n 的质因子对题目的影响。然后因为剩下的质因子只会出现至多一次,
因为对于n,质因数分解,最多只有一个质因子大于√n.再考虑这个大质数放哪个集合
我们记 f(S 1 , S 2 ) 表示两个集合中出现的质因子集合分别为S 1 , S 2 的方案数。
dp(j ,k, t) 为甲拿s1,乙拿s2,这个大质数放入t集合. t=0 or 1;
这样,转移的时候就只需考虑包含当前这个大于 n 的质因子的数放入那个集合中即可。
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
int pri[8]={2,3,5,7,11,13,17,19},n,mod;    
int f[500][500],dp[500][500][2];
int ans=0;
struct node{
    int a,p;            //a:这个数的质因数集合  p:大质数
    bool operator < (const node & x) const 
    {  return p<x.p;  }
}num[501];
void init(){
    f[0][0]=1;
    For(i,2,n){
        int tmp=i;
        For(j,0,7)
            if(tmp%pri[j]==0){
                while(tmp%pri[j]==0) tmp/=pri[j];
                num[i].a|=(1<<j);
                }
        num[i].p=tmp;   //不能被那八个质数除的部分为要求的大质数
        }
    }
int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    scanf("%d%d",&n,&mod);
    init();         // 预处理每个数的质因数集合与它的大质数
    sort(num+2,num+n+1);    //把质数相同的放一起讨论
    For(i,2,n){
        if(num[i].p==1 || num[i].p!=num[i-1].p){    //如果全在集合or换了一个质数,就是不同类.同类不能同时选
            For(j,0,256) For(k,0,256)
                dp[j][k][0]=dp[j][k][1]=f[j][k];
        }

        Rep(j,256,0) Rep(k,256,0){      
           if((num[i].a & k) == 0)          // 如果该数集合与第二个人没交集,则可以放入第一个人集合
             dp[j | num[i].a][k][1] = ((LL)dp[j | num[i].a][k][1]+ dp[j][k][1]) % mod;
           if((num[i].a & j) == 0)          // 同理放入第二个集合
             dp[j][k | num[i].a][0] = ((LL)dp[j][k | num[i].a][0] + dp[j][k][0]) % mod;
        }

        if(num[i].p==1 || num[i].p!=num[i+1].p)     //每次换类别时要改变方案情况
            Rep(j,256,0) Rep(k,256,0)
                f[j][k]=((LL)dp[j][k][0]+dp[j][k][1]-f[j][k])%mod;  //这个数两人都不选时dp0 dp1都有包含
    }

    For(i,0,256) For(j,0,256)
        if((i&j) == 0)
            (ans+=f[i][j])%=mod;

    printf("%d\n",(ans+mod)%mod);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值