NOIP模拟 乘积 - 状压dp + 分组背包

本文介绍了一道组合数学题目,利用状压DP和分组背包的方法解决求解特定条件下乘积为无平方因子数的组合方案数量的问题。通过对数的质因数分解并分组,结合动态规划技巧,有效地降低了问题的时间与空间复杂度。

题目大意:

给出n和k,求从小于等于n的数中取出不超过k个,其乘积是无平方因子数的方案数。无平方因子数:不能被质数的平方整除。

题目分析:

10(枚举\(n\le8\)),40(简单状压\(n\le16\)),70(高级状压\(n\le30\)),100(正解状压n\le500,k\le500)。
对于前百分之70,由于\(n\le30\),质数只有10个,直接状压水。

正解(状压dp+分组背包):
注意到1~n中每个数含有的大于\(\sqrt{n}\)的质因数最多有1种,而\(\sqrt{n}\)内的质数只有8个,于是可以分别对待:
将1~n的数进行分组,含有大于\(\sqrt{n}\)的数分为一组,不含的单独成组,组内的每个元素用8位二进制表示该数含有的小于等于\(\sqrt{n}\)的质因子。
这样分组是因为小于等于\(\sqrt{n}\)的质数只有8个可以方便的进行状压dp,而含有大于\(\sqrt{n}\)质因子(只有1种)的数无法进行状压(时空不够),于是将其放在一个组内,dp时只取出该组中的一个数(保证大于\(\sqrt{n}\)的质因子不平方),从而降低时空。

code:

#include<bits/stdc++.h>
using namespace std;
const int N = 550, Mod = 1e9+7;
int T, n, k, dp[N][(1<<8)+50], state[N], val[N];
const int prime[10] = {2, 3, 5, 7, 11, 13, 17, 19};
vector<int> vec[N];
int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        for(register int i = 1; i <= n; i++) vec[i].clear(), state[i] = 0;
        for(register int i = 1; i <= n; i++){
            val[i] = i;
            for(register int j = 0; j < 8; j++){
                if(val[i] % prime[j] == 0){
                    val[i] /= prime[j];
                    if(val[i] % prime[j] == 0){ 
                        state[i] = -1;  //能够整除质数平方不合法
                    }
                    else state[i] |= (1 << j);
                }
            }
        }
        for(register int i = 1; i <= n; i++){
            if(state[i] == -1) continue;
            if(val[i] == 1){
                vec[i].push_back(i);  //不包含大于sqrtn的质因数 
            }
            else vec[val[i]].push_back(i);  //大于等于sqrtn的质因数只可能有一个 
        }
        for(register int i = 1; i <= n; i++)
            for(register int j = 0; j < 256; j++)
                dp[i][j] = 0;
        dp[0][0] = 1;
        for(register int i = 1; i <= n; i++){
            int vecSze = vec[i].size();
            static int tmp[N];
            for(register int j = 0; j < vecSze; j++) tmp[j] = state[vec[i][j]];  //卡常操作
            for(register int l = k; l >= 1; l--){                //一组之中只能选择一个状态,l倒序 
                for(register int j = 0; j < vecSze; j++){                            
                    for(register int sta = (255^tmp[j]); ; sta = (255^tmp[j]) & (sta - 1)){  //枚举补集,减少一些赘余的状态 
                        dp[l][sta | tmp[j]] = (dp[l][sta | tmp[j]] + dp[l-1][sta]) % Mod;
                        if(!sta) break;
                    } 
                } 
            } 
        }
        int ans = 0;
        for(register int i = 0; i < 256; i++){
            for(register int j = 1; j <= k; j++)
                ans = (ans + dp[j][i]) % Mod;
        }
        printf("%d\n", ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/CzYoL/p/7725083.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值