状压dp--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

n<=500 p<=1e9

solution:
选数等价于选了这个数所有的质因子,所以按质因子来考虑问题,
即两个集合不能有相同的质因子
先考虑n 很小比如30 时,则2 到30 间的质数只有10 个,可以考虑状态压缩
f[i][A][B] 表示前i 个数,A 集合所选质数状态为A,B 集合所选质数状态为B 的方案数
预处理出每个数的质因子分解并转成二进制状态pi,转移的时候有三种选择,对应着f[i + 1][A][B]; f[i + 1][Ajpi][B]; f[i + 1][A][Bjpi]
最后枚举A; B 并将A&B 为0 的状态的值统计进答案
现在n 很大,质数变多了无法状压,还需要进一步考虑

注意到每个数至多包含一个>sqrt(n) 的质因子,而<=sqrt(n) 的质因子一共只有9 个,
所以考虑状压小质因子,暴力算大质因子
f[A][B] 表示前i 个大质数,A 集合包含的小质数状态为A,B 集合包含的小质数状态为B 的方案数
转移时,枚举包含i 的所有数,此时转移与之前的暴力类似有三种选择
注意此时所有数都含某个大质因子,所以要么都放入A 要么都放入B 要么都不放
可以利用一个辅助数组g[0/1][A][B] 来帮助计算这一个过程
时间复杂度O(n*2^18)

状压dp好题啊
关于互质一般就会想到质因子,因为质因子一般会很少并且满足一些性质就像上面说的
而质因子很少的时候可以考虑状压,dp的时候有一些额外需要的信息
可以开辅助数组来记录
还有注意状压的数组空间和时间复杂度

具体细节看注释:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 505
#define LL long long
#define re register
using namespace std;
int n;
int pri[15]={2,3,5,7,11,13,17,19,23};
LL p,f[1<<9][1<<9],g[2][1<<9][1<<9],ans;//我的妈这里开成1<<10居然也会t
const int mx=255;//注意这里开成511就会t掉一个点

inline LL rd(){
  LL x=0,f=1;char c=' ';
  while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
  while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
  return x*f;
}

struct Sushi{
  int S,prime;
}a[maxn];
inline bool cmp(Sushi x,Sushi y){return x.prime<y.prime;}

int main(){
  n=rd(); p=rd();
  for(re int i=1;i<=n;i++){
    int x=i;
    for(re int j=0;j<8;j++){
      if(x%pri[j]>0) continue;
      if(x<pri[j]) break;
      a[i].S|=(1<<j);
      while(x%pri[j]==0) x/=pri[j];
    }
    a[i].prime=x;
  }
  sort(a+2,a+n+1,cmp);//要排序
  f[0][0]=1;
  for(re int i=2;i<=n;i++){//这里是判断这个数是包含这个大质数的数的第一个
    if(i==2 || a[i].prime!=a[i-1].prime || a[i].prime==1){
      memcpy(g[0],f,sizeof f); memcpy(g[1],f,sizeof f);
    }
    for(re int j=mx;j>=0;j--)
      for(re int k=mx;k>=0;k--){//这里必须倒着计算,不然可能算重了
        if(j&k) continue;
        if((a[i].S&k)==0)//放到第一个里行不行
          (g[0][a[i].S|j][k]+=g[0][j][k])%=p;
        if((a[i].S&j)==0)
          (g[1][j][a[i].S|k]+=g[1][j][k])%=p;
      }
    if(i==n || a[i].prime==1 || a[i].prime!=a[i+1].prime)//这里判断这个数是包含这个大质数的最后一个
      for(re int j=0;j<=mx;j++)
        for(re int k=0;k<=mx;k++){
          if(j&k)continue;
          f[j][k]=g[0][j][k]+g[1][j][k]-f[j][k];
          (f[j][k]+=p)%=p;//注意这里可能是负数
        }//这里g0和g1都计算过两遍f[j][k],所以要减掉一个
  }
  for(re int i=0;i<=mx;i++)
    for(re int j=0;j<=mx;j++)
      if(!(i&j)) (ans+=f[i][j])%=p;
  printf("%lld\n",ans);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值