【状压DP】【cofun1623】寿司晚宴

【cofun1623】寿司晚宴

这里写图片描述

Sample Input
输入样例1
3 10000
输入样例2
4 10000
输入样例3
100 100000000
Sample Output
样例输出1
9
样例输出2
21
样例输出3
3107203

Hint
这里写图片描述


  • 分析:
    题意:把一个数的集合分为两个集合,记为G,M,
    显然,G中数的质因子与M中数的质因子应互不相同,因此选了一个数等同于选择了它的质因子。
    虽然2<=n<=500,数据比较大,但是经过计算,我们会发现每个数最多只能有一个>= n 的质因子,其余质因子都< n ;而< n 的质数只有8个,加上对题目理解,可以考虑状压DP。

    1. 把每个数中< 500 的质因子压进二进制数中,并把它>= 500 的质因子记录下来。
    2. 按照最大质因子的大小给寿司排序,方便DP。
    3. 状压DP:
      令f[i][j]表示集合G中质因子状态为i,集合M中质因子状态为j的方案数;dp[0][i][j]表示新出现的大质数归入集合G,原先G状态为i,M状态为j的方案数;dp[1][i][j]表示新出现的大质数归入集合M,原先G状态为i,M状态为j的方案数;
      当枚举到新的大质数时,分情况,即不归入(不DP),归入G和归入M,可平行状压DP;
      当含该新大质数的状态枚举完后,应将dp[0][i][j]和dp[1][i][j]归并为f[i][j],注意归并时因dp[0][i][j]和dp[1][i][j]都包含了不归入新的大质数的状态,应再减去一次,因类似背包,直接-f[i][j]即可。
      注意:运用背包思想降了一维是表示当前枚举到第几个大质数,所以dp转移时要倒序。
      具体转移方程在代码中注解。【因为比较多判断条件,会影响美观XDDD

【做这题的时候看了dalao们的题解,感觉不是很能理解,还是自己打舒服~


  • 代码:
#include <bits/stdc++.h>
 using namespace std;

 const int su[8] = {2, 3, 5, 7, 11, 13, 17, 19};
 struct info{
    int da, su;
 }e[505];
 long long  n, m, p, i, j, k, ans, dp[2][1 << 8][1 << 8], f[1 << 8][1 << 8]; 

 inline int read()
 {
    long long x = 0, w = 1;
    char ch = 0;
    while(ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
     }
    while(ch >= '0' && ch <= '9')
          x = x * 10 + ch - '0', ch = getchar();
    return x * w;
  } //读入优化

 inline void write(long long x)
 {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
 }//输出优化

 bool cmp(info a, info b)
 {
    return a.da < b.da || (a.da == b.da && a.su < b.su);
 }

 int main()
 {

    n = read(), p = read();
    //读入 
    for(i = 2; i <= n; i ++)
    {
        m ++;
        for(j = 0, k = i; j < 8; j ++)
        if (k % su[j] == 0)
        {
            for(; k % su[j] == 0; k /= su[j]);
            e[m].su |= (1 << j);
         }          
        e[m].da = k;
     }
    sort(e + 1, e + m + 1, cmp);
    //把美味度的质因子压进二进制并按大质数排序1.&2. 
    f[0][0] = 1;
    for(i = 1; i <= m; i ++)
    {
        if (e[i].da == 1 || e[i].da != e[i - 1].da)
        {
            memcpy(dp[0], f, sizeof(f));
            memcpy(dp[1], f, sizeof(f));
         }
        //新出现的大质数不归入集合G与M 
        for(j = (1 << 8) - 1; j >= 0; j --)
            for(k = (1 << 8) - 1; k >= 0; k --)
            {
                if (! (e[i].su & k))
                    dp[0][j | e[i].su][k] = (dp[0][j | e[i].su][k] + dp[0][j][k]) % p;
                //新出现的大质数归入集合G 
                if (! (e[i].su & j))
                    dp[1][j][k | e[i].su] = (dp[1][j][k | e[i].su] + dp[1][j][k]) % p;
                //新出现的大质数归入集合M 
             }
        //新出现的大质数归入集合G或M,同时将小质数的状态进行状压DP     
        if (e[i].da == 1 || e[i].da != e[i + 1].da)
            for(j = 0; j < 1 << 8; j ++)
                for(k = 0; k < 1 << 8; k ++)
                    f[j][k] = ((dp[0][j][k] - f[j][k]) % p + dp[1][j][k] + p) % p;
        //将两种平行的情况归并 
     }
    //DP3. 
    for(j = 0; j < 1 << 8; j ++)
        for(k = 0; k < 1 << 8; k ++)
        if (! (j & k))
            ans = (ans + f[j][k]) % p;
    //统计答案 
    write(ans);
    //输出 
    return 0;

 }

下午好咩咩咩鹿啦啦~


已经晚上19:23了,上来抒发一下内心的咩咩皮(MMP),我在下一篇【队伍统计】中写跟同学讲了蛮久的寿司晚宴,然后,刚刚才帮他把这题啪对。。很多小细节都会导致致命的错误,他居然在转移方程里多了个+号,看了贼久ODX所以做题时一定要仔细啊!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值