#状压dp,容斥#洛谷 2150 JZOJ 5731 寿司晚宴

博客围绕将2至n分成两个集合,使两集合元素乘积最大公约数为1的问题展开。先提出状态压缩dp思路,但因时间复杂度大需优化。考虑500以内数最多有一个大质因数,对大质数排序,分f1、f2、dp三部分处理,最后依据容斥定理得出结果,时间复杂度为O(216n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

2∼n2\sim n2n分成两个不必非空的集合S1,S2S_1,S_2S1,S2,问有多少种方法使gcd(∏S1,∏S2)=1gcd(\prod S_1,\prod S_2)=1gcd(S1,S2)=1


分析

那么很容易想到状态压缩dp,设dp[S1][S2]dp[S_1][S_2]dp[S1][S2]表示第一个集合为S1S_1S1,第二个集合为S2S_2S2的方案数,那么dp[S1∣k][S2]+=dp[S1][S2][S2dp[S_1|k][S_2]+=dp[S_1][S_2][S_2dp[S1k][S2]+=dp[S1][S2][S2 and k=0]k=0]k=0]S2S_2S2也类似,最后把所有答案都累加,但是这样还是有点问题,因为这样时间复杂度太大了,我们考虑优化,500以内最多有一个大质因数,所以可以处理每个数所存在的大质数,对它们进行大到小排序,要把数组分为三个部分,f1f1f1也就是选择S1S_1S1,f2f2f2也就是选择S2S_2S2,dpdpdp也就是总选择方案,那么首先若所含的大质数与上一个不同或不含大质数,那么就把f1,f2f1,f2f1,f2赋值为dpdpdp,否则接着上一个,然后f1,f2f1,f2f1,f2按照同样的方法,然后赋值给dpdpdp的时候必然是不能重复计算f1,f2f1,f2f1,f2,所以不能是同一个大质数或不含大质数,就可以了,但是发现f1,f2f1,f2f1,f2都是由dpdpdp得到的,所以按照容斥定理,dp=f1+f2−dpdp=f1+f2-dpdp=f1+f2dp,只有8个小质数,所以时间复杂度是O(216n)O(2^{16}n)O(216n)


代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define rr register
using namespace std;
const int prime[8]={2,3,5,7,11,13,17,19};
struct rec{
    int w,big,dat;
    inline void init(){
        rr int t=w;
        for (rr int i=0;i<8;++i)
        if (t%prime[i]==0){
            dat|=1<<i;
            while (t%prime[i]==0) t/=prime[i];
        }
        big=t;
    }
    bool operator <(const rec &t)const{
        return big<t.big||(big==t.big&&w<t.w);
    }
}a[501];
int f1[261][261],f2[261][261],dp[261][261],n,mod,ans;
inline void modd(int &a,int b){
    a=a+b; a=(a<0)?(a+mod):a; 
    a=(a>=mod)?(a-mod):a;
}
signed main(){
    scanf("%d%d",&n,&mod);
    for (rr int i=1;i<n;++i) a[i].w=i+1,a[i].init();
    sort(a+1,a+n); dp[0][0]=1;
    for (rr int i=1;i<n;++i){
        if (a[i].big!=a[i-1].big||a[i].big==1){
            memcpy(f1,dp,sizeof(f1));
            memcpy(f2,dp,sizeof(f2));
        }
        for (rr int j=255;~j;--j)
        for (rr int k=255;~k;--k){
            if (j&k) continue;
            if (!(a[i].dat&j)) modd(f2[j][a[i].dat|k],f2[j][k]);
            if (!(a[i].dat&k)) modd(f1[j|a[i].dat][k],f1[j][k]);
        }
        if (a[i].big!=a[i+1].big||a[i].big==1)
        for (rr int j=255;~j;--j)
            for (rr int k=255;~k;--k)
                if (!(j&k))
                modd(dp[j][k]*=-1,f1[j][k]+f2[j][k]);
    }
    for (rr int j=255;~j;--j)
    for (rr int k=255;~k;--k)
        if (!(j&k)) modd(ans,dp[j][k]);
    return !printf("%d",(ans+mod)%mod);
}
### 关于缩动态规划(DP)的相关题目与教程 #### 洛谷平台上的资源推荐 洛谷作为一个优质的在线编程练习网站,提供了大量有关缩动态规划的学习资料和实战题目。对于想要深入理解并掌握这一复杂算法的人来说,这些资源非常有价值。 #### 推荐的经典入门题库 - **P1433 吃奶酪**:这道题不仅涉及到了深度优先搜索加缩的思想,还融合了动态规划的概念,在解决过程中能够很好地锻炼选手对多种算法组合运用的能力[^2]。 - **PRZ - POI2004**:此问题同样是一道典型的缩动态规划应用实例,通过该题目的训练可以帮助加深对这类问题处理方法的理解[^3]。 #### 学习路径建议 为了更好地理解和实践缩动态规划,建议按照如下顺序逐步推进: - 熟悉二进制位运算操作的基础知识,这是实现高效编码的关键技能之一; - 复习基本的动态规划理论及其常见应用场景; - 结合具体案例研究如何定义合适的态表示形式以及设计有效的态转移方程; ```python def dp_solution(): """ 这里提供了一个简化版的缩动态规划框架, 实际编写时需根据具体问题调整参数设置及逻辑流程。 """ n = ... # 输入规模大小 state_size = 1 << n # 总共可能存在的不同态数 # 初始化记忆化数组,默认值设为无穷大或其他不可能取到的最大/最小边界条件 memo = [-1] * state_size def dfs(current_state, current_index): nonlocal memo if all_bits_set(current_state): return cost_to_end[current_index] if memo[current_state] != -1: return memo[current_state] min_cost = float(&#39;inf&#39;) for next_index in range(n): new_state = update_state_with_next_move(current_state, next_index) transition_cost = calculate_transition_expense(current_index, next_index) total_cost = transition_cost + dfs(new_state, next_index) min_cost = min(min_cost, total_cost) memo[current_state] = min_cost return min_cost result = dfs(initial_state(), start_position()) return result ``` 上述代码片段展示了利用函数`dfs()`来进行带备忘录的记忆化搜索过程,其中包含了几个重要的组成部分如初始化全局变量、判断终止条件、剪枝优化等技巧来提高求解效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值