【NOI2015】【bzoj4197】【状压DP】【滚动数组】寿司晚宴

Description

为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n)。
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。

Input

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

Output

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

Sample Input

3 10000

Sample Output

9

HINT

2≤n≤500
0< p≤1000000000

状态压缩DP,我写的 O(n×48) 好像还有一个 O(n×38) 我不会,网上说得简洁,我蒟蒻无比,看不懂,就只会写这个 48
然后大概说一下怎么DP
我们选取一个数,实际上就是选它的质因数,而500以内质数有95个明显无法装压,但我们还可以发现,500以内每个数大于 500 的质因子最多只有一个,而小于 500 的质数一共只有8个,我们就可以考虑装压了
我们建立两个数组f[k][i][j]表示考虑前k个数,A的状态为i,B的状态为j有多少个情况,dp[0/1][k][i][j]表示正在考虑第k个数,A/B拿走第k个数,拿走后A状态为i,B为j


然后转移方程可以列出:
dp[0][k][i|p(k)][j]=f[k-1][i|p(k)][j]+dp[0][k][i][j]
dp[1][k][i][j|p(k)]=f[k-1][i][j|p(k)]+dp[1][k][i][j]
f[k][i][j]=dp[0][k][i][j]+dp[1][k][i][j]-f[k-1][i][j]


但是数组开不下,我们就可以滚动,这时才有了我一直没看懂的转移方程
dp[0][i|p(k)][j]=dp[0][i|p(k)][j]+dp[0][i][j]
dp[1][i][j|p(k)]=dp[1][i][j|p(k)]+dp[1][i][j]
f[i][j]=dp[0][i][j]+dp[1][i][j]-f[i][j]
然后我们先处理前面全部属于那8个质数的,再一个一个大质数的枚举,处理就可以了


程序如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<algorithm>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<stack>
#define INF 2100000000
#define ll long long
#define clr(x)  memset(x,0,sizeof(x))
#define clrmax(x)  memset(x,127,sizeof(x))

using namespace std;

inline int read()
{
    char c;
    int ret=0;
    while(!(c>='0'&&c<='9'))
        c=getchar();
    while(c>='0'&&c<='9')
    {
        ret=(c-'0')+(ret<<1)+(ret<<3);
        c=getchar();
    }
    return ret;
}

#define M 500
#define N (1<<8)

int f[N][N],dp[2][N][N],n,P,t;
const int pri[]={2,3,5,7,11,13,17,19};

struct node
{
    int x,num;
    node(int x=0,int num=0):x(x),num(num){}
}a[M];
#define x(y) a[y].x
#define num(x) a[x].num

bool com(node a,node b)
{
    return a.x<b.x;
}

void add(int x)
{
    int temp=0;
    for(int i=0;i<8;i++)
        while(x%pri[i]==0)
        {
            x/=pri[i];
            temp|=1<<i;
        }
    a[++t]=node(x,temp);
}

void DP()
{
    f[0][0]=1;
    for(int l=1,r;l<=t;l=r+1)
    {
        for(r=l;x(r+1)==x(r)&&x(r)!=1;r++);
        memcpy(dp[0],f,sizeof(f));memcpy(dp[1],f,sizeof(f));
        for(int k=l;k<=r;k++)
        {
            for(int i=255;~i;i--)
            {
                int now=255^i;
                for(int j=now;;j=(j-1)&now)
                {
                    if((j&num(k))==0)
                        dp[0][i|num(k)][j]=(dp[0][i|num(k)][j]+dp[0][i][j])%P;
                    if((i&num(k))==0)
                        dp[1][i][j|num(k)]=(dp[1][i][j|num(k)]+dp[1][i][j])%P;
                    if(!j)break;
                }
            }
        }
        for(int i=0;i<=255;i++)
            for(int j=0;j<=255;j++)
                f[i][j]=((dp[0][i][j]+dp[1][i][j]-f[i][j])%P+P)%P;
    }
    int ans=0;
    for(int i=0;i<=255;i++)
    {
        int now=255^i;
        for(int j=now;;j=(j-1)&now)
        {
            ans=(ans+f[i][j])%P;
            if(!j)break;
        }

    }
    printf("%d",ans);
}

int main()
{
    //freopen("in.txt","r",stdin);
    n=read();P=read();
    for(int i=2;i<=n;i++)
        add(i);
    sort(a+1,a+t+1,com);
    DP();
    return 0;
}

大概就是这个样子,如果有什么问题,或错误,请在评论区提出,谢谢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值