[Noi2015]寿司晚宴

[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取模)。注意一个人可以不吃任何寿司

思路

比较显然的结论是:当一个人选择一个数时,就相当于选择了它的质因数集。若用两个二进制数s1,s2表示两个人现在已经拥有哪些质因数时,一定满足s1 or s2 == 0.

有了这个结论,\(n<=30\)的情况已经可以解决了.状压\(dp\)即可。

随后,可以知道,若\(n<=500\),那么每个数最多只有一个大于等于\(\sqrt{n}\)的质因数,那么,只需要额外考虑含有这些“大质因数”的数即可。显然,这些质因数不能同时出现在两个人手中。那么,我们将\(dp\)转移稍微修改一下。

我们把所有大质因数相同的数放一起算。
\[ f[0][bit],表示当前把数给小G(或不拿)的方案数 \]

\[ f[1][bit],表示当前把数给小W(或不拿)的方案数 \]
转移和之前一样,只不过\(f[0]\)只转移给\(f[0]\),\(f[1]\)只转移给\(f[1]\).每当一个大质因数算完后,再去重(不拿的情况会被算到两次)。

若没有大质因数,则每算一次都去重即可。

#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=(l),i##R=(r);i<=i##R;i++)
#define DOR(i,r,l) for(int i=(r),i##L=(l);i>=i##L;i--)
#define loop(i,n) for(int i=0,i##R=(n);i<i##R;i++)
#define mms(a,x) memset(a,x,sizeof a)
using namespace std;
typedef long long ll;
template<typename A,typename B>inline void chkmax(A &x,const B y){if(x<y)x=y;}
template<typename A,typename B>inline void chkmin(A &x,const B y){if(x>y)x=y;}
const int N=505;
bool mm1;
int n;ll P,ans;
void Add(ll &x,ll y){
    x+=y;
    if(x>=P)x-=P;
}
void Mul(ll &x,ll y){
    x=x*y%P;
}
int pri[N],ct;
bool check(int x){
    for(int i=2;i*i<=x;i++)
        if(x%i==0)return false;
    return true;
}
ll f[3][1<<10][1<<10];
//f[2]为合并后总方案
struct node{
    int st,p;
    bool operator<(const node &A)const{
        return p<A.p;
    }
}A[N];
void solve(){
    for(int i=2;i*i<=500;i++)if(check(i))pri[ct++]=i;
    f[2][0][0]=1;
    FOR(i,2,n){
        int st=0,k=i;
        loop(j,ct)if(k%pri[j]==0){
            st|=(1<<j);
            while(k%pri[j]==0)k/=pri[j];//预处理大质因数(1则为没有)
        }
        A[i]=(node){st,k};
    }
    sort(A+2,A+n+1);//使大质因数相同的和一起算
    FOR(i,2,n){
        if(i==2||A[i].p!=A[i-1].p||A[i].p==1){//若开始计算新的大质因数,则将f[1]和f[0]设为当前答案
            DOR(j,(1<<ct)-1,0)DOR(k,(1<<ct)-1,0)
                f[0][j][k]=f[1][j][k]=f[2][j][k];
        }
        DOR(j,(1<<ct)-1,0)DOR(k,(1<<ct)-1,0){
            if(j&k)continue;
            if((j&A[i].st)==0)Add(f[0][j][k|A[i].st],f[0][j][k]);
            if((k&A[i].st)==0)Add(f[1][j|A[i].st][k],f[1][j][k]);
        }
        if(i==n||A[i].p==1||A[i].p!=A[i+1].p){
            DOR(j,(1<<ct)-1,0)DOR(k,(1<<ct)-1,0){
                if(j&k)continue;
                f[2][j][k]=f[0][j][k]+f[1][j][k]-f[2][j][k];
                f[2][j][k]=(f[2][j][k]%P+P)%P;
            }
        }
    }
    DOR(j,(1<<ct)-1,0)DOR(k,(1<<ct)-1,0)
        Add(ans,f[2][j][k]);
    printf("%lld\n",ans);
}
int main(){
    scanf("%d%lld",&n,&P);
    solve();
    return 0;
}

转载于:https://www.cnblogs.com/Heinz/p/11466154.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值