cf895c 状压dp+技巧

本文探讨了质因子的计数方法,特别是在处理小于等于31的质因子时的优化策略。通过状态压缩和组合数学,文章提供了一种高效算法来解决特定问题,如在给定数字集合中寻找特定条件下的组合数量。

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

质因子到31,有11个,状压2^11~2000, >=37单独考虑,因为37*2>70;

n个中取偶个,取法数为2^(n-1); (1-1)^2 二项式展可得

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>

using namespace std;

int n;
int a[100005];
int primes[105];
int cnt;
int isp[10005];

void init()
{
    for(int i=2;i<=100;i++)
    {
        if(isp[i]==0)
            primes[cnt++]=i;
        for(int j=0;j<cnt;j++)
        {
            isp[primes[j]*i]=1;
            if(i%primes[j]==0)
                break;
        }
    }

}

long long mod=1e9+7;
long long dp[2][4005];

long long fac[100005];
int ct[85];
int main() {
    init();
    //cout<<primes[11]<<endl;
    fac[0]=1;
    for(int i=1;i<=100000;i++)
    {
        fac[i]=fac[i-1]*2%mod;
    }

    while(~scanf("%d",&n))
    {
        memset(dp,0,sizeof(dp));
        memset(ct,0,sizeof(ct));
        dp[0][0]=1;
        int cur=0;
        for(int i=0;i<n;i++)
        {
            int tp;
            scanf("%d",&tp);
            if(tp>35 && isp[tp]==0)
            {
                ct[tp]++;
                continue;
            }

            int tid=0;
            for(int j=0;j<11;j++)
            {
                int tcnt=0;
                while(tp%primes[j]==0)
                    tcnt++,tp/=primes[j];

                tcnt%=2;
                tid+=(tcnt<<j);
            }


            memset(dp[cur^1],0,sizeof(dp[cur^1]));
            for(int j=0;j<(1<<11);j++)
            {
                if(tp==1) {
                    int nid = (j ^ tid);
                    dp[cur ^ 1][nid] += dp[cur][j];
                    dp[cur ^ 1][nid] %= mod;
                }
                dp[cur^1][j]+=dp[cur][j];
                dp[cur^1][j]%=mod;
            }

            cur^=1;
        }
        long long ans=dp[cur][0];
        for(int i=37;i<=70;i++)
            if(ct[i]!=0)
                ans=ans*fac[ct[i]-1]%mod;

        printf("%lld\n",((ans-1)%mod+mod)%mod);
    }
}

 

<think>我们正在讨论C++中的upper_bound函数的使用。根据用户的问题,他想要理解并优化代码片段:`upper_bound(dp+1, dp+res+1, f[i]) - dp` 的作用和用法。 首先,我们来分析这个表达式: - `upper_bound` 是C++标准库中的一个函数,用于在已排序的序列中查找第一个大于给定值的元素的位置。 - 它的基本用法是:`upper_bound(first, last, value)`,其中`first`和`last`是序列的迭代器(或指针),`value`是要查找的值。 - 它返回一个迭代器(或指针),指向序列中第一个大于`value`的元素。如果找不到这样的元素,则返回`last`。 在表达式`upper_bound(dp+1, dp+res+1, f[i]) - dp`中: - `dp` 是一个数组(或指针),`dp+1`指向数组的第二个元素(索引1),`dp+res+1`指向数组索引为`res+1`的位置(即范围是`[1, res]`,因为序列从`dp+1`开始,到`dp+res+1`结束,实际上是左闭右开区间`[dp+1, dp+res+1)`)。 - 因此,`upper_bound(dp+1, dp+res+1, f[i])` 会在数组`dp`的子数组(从索引1到索引`res`)中查找第一个大于`f[i]`的元素的位置。 - 然后,减去`dp`(数组的起始地址)得到的是该元素在数组中的索引(下标)。因为`dp`是数组的起始地址,所以`指针 - dp`得到的就是从0开始的下标。但是注意,这里搜索的范围是从`dp+1`开始,所以返回的下标可能是从1到`res+1`(如果找不到,则返回`dp+res+1`,减去`dp`后得到`res+1`)。 例如,假设`dp`数组从索引0开始,但这里我们使用索引1到`res`(即`dp[1]`到`dp[res]`)。那么,`upper_bound(dp+1, dp+res+1, f[i])`返回的指针可能是`dp+k`(其中`k`在1到`res+1`之间),然后减去`dp`得到`k`(即下标k)。注意,如果返回的是`dp+res+1`,那么减去`dp`就是`res+1`。 这个表达式的作用是:在`dp`数组的[1, res]区间内,找到第一个大于`f[i]`的元素的位置(下标)。如果所有元素都不大于`f[i]`,则返回`res+1`。 这种用法常见于动态规划中维护一个有序的序列,特别是用于优化最长上升子序列(LIS)等问题的O(nlogn)解法。在LIS问题中,我们经常维护一个数组`dp`,其中`dp[i]`表示长度为i的上升子序列的最后一个元素的最小值。然后,对于每个新元素`f[i]`,我们使用`upper_bound`(或`lower_bound`)来查找插入位置,从而更新`dp`数组。 在最长上升子序列问题中,通常我们使用`lower_bound`来查找第一个大于等于当前元素的位置(用于非严格递增)或者严格递增时使用`lower_bound`(因为要求严格递增时,如果存在相等的元素,则不能放在同一个长度上,但实际做法中,我们通常用`lower_bound`来找到替换位置)。而`upper_bound`则用于严格递增序列中,查找第一个大于当前元素的位置(即替换位置)。 然而,在用户给出的代码片段中,使用的是`upper_bound`,所以它查找的是第一个大于`f[i]`的位置。这个位置可以用于替换操作,从而维护一个严格递增的序列。 例如,在最长递增子序列的O(nlogn)算法中: - 我们维护一个数组`dp`,`dp[1...res]`是当前长度为1,2,...,res的递增子序列的末尾元素的最小值,并且这个数组是递增的。 - 对于每个元素`a[i]`: - 如果`a[i]`大于`dp[res]`,则扩展:`dp[++res] = a[i]`。 - 否则,在`dp[1..res]`中找到第一个大于`a[i]`的位置`pos`(即`upper_bound`返回的位置),然后令`dp[pos] = a[i]`。 这里,`pos = upper_bound(dp+1, dp+res+1, a[i]) - dp`,然后更新`dp[pos] = a[i]`。这样,我们保证了`dp`数组的单调性,并且用更小的值替换,使得后续扩展的可能性更大。 因此,用户代码中的表达式就是用来获取这个位置的索引。 关于优化:使用`upper_bound`本身已经是二分查找,时间复杂度为O(logn),所以效率已经很高。但我们可以考虑以下几点优化: 1. 确保数组`dp`是严格递增的,这样`upper_bound`才能正确工作。 2. 如果数组`dp`的大小(即`res`)很大,可以考虑使用更高效的二分查找实现,但标准库的`upper_bound`已经足够高效。 3. 在循环中,如果`f[i]`比当前`dp`数组的最大值(即`dp[res]`)还要大,那么可以直接添加到末尾,避免二分查找。即: ```c++ if (f[i] > dp[res]) { dp[++res] = f[i]; } else { int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; dp[pos] = f[i]; } ``` 这样可以减少一些不必要的查找(虽然最坏情况下还是O(logn))。 4. 注意数组边界:确保`res`在合理范围内,避免数组越界。 另外,用户提供的引用中,引用[1]和[4]提到了`lower_bound`和`upper_bound`的用法,引用[2]和[5]则展示了一些动态规划的代码,其中引用[2]的代码中使用了`lower_bound`。我们可以参考这些代码来理解。 在引用[2]的代码中,有: ```c++ int now = e[i].x - e[i].len; int num = lower_bound(cf+1, cf+1+n, now) - cf; ``` 这里,`lower_bound`在数组`cf`(从1到n)中查找第一个大于等于`now`的位置,返回该位置的指针,减去`cf`得到下标`num`。如果找不到,则返回`n+1`。 而用户询问的是`upper_bound`,它与`lower_bound`的区别在于: - `lower_bound`:返回第一个大于等于value的元素位置。 - `upper_bound`:返回第一个大于value的元素位置。 因此,在需要严格大于的情况下,使用`upper_bound`。 总结: - 表达式`upper_bound(dp+1, dp+res+1, f[i]) - dp`的作用:在数组`dp`的[1, res]范围内(即从`dp[1]`到`dp[res]`)查找第一个大于`f[i]`的元素的下标(如果找不到,返回`res+1`)。 - 用法:通常用于动态规划中维护单调序列,比如优化最长上升子序列(LIS)问题。 - 优化:可以添加一个判断,如果当前值大于序列最大值则直接扩展,避免二分查找。 最后,根据系统指令,我们需要生成相关问题: §§相关问题§§ 1. 在什么情况下应该使用`upper_bound`而不是`lower_bound`? 2. 如何用`upper_bound`实现最长上升子序列(LIS)的O(nlogn)算法? 3. 在使用`upper_bound`时,如何处理数组下标从0开始的情况? 4. 除了动态规划,`upper_bound`还有哪些常见的应用场景?</think>### `upper_bound(dp+1, dp+res+1, f[i]) - dp` 的作用和用法详解 #### 1. **核心作用** 该表达式在已排序的数组 `dp` 的子区间 `[1, res]` 中,**查找第一个严格大于 `f[i]` 的元素的位置(下标)**。返回值是一个整数下标,范围在 `1` 到 `res+1` 之间: - 若找到满足条件的元素,返回其下标(如 `k`)。 - 若所有元素均不大于 `f[i]`,则返回 `res+1`(表示越界位置)。 #### 2. **关键语法解析** - **`upper_bound(first, last, value)`** - `first`:搜索区间的起始指针(`dp+1` 指向 `dp[1]`)。 - `last`:搜索区间的结束指针(`dp+res+1` 指向 `dp[res]` 的下一个位置,即左闭右开区间 `[dp+1, dp+res+1)`)。 - `value`:待比较的值(`f[i]`)。 - **`- dp` 的意义** 将返回的指针转换为下标(指针差值)。若返回指针为 `dp+k`,则 `(dp+k) - dp = k`(即下标 `k`)。 #### 3. **典型应用场景** 常见于动态规划(DP)中维护**单调递增序列**,例如优化最长上升子序列(LIS)问题: ```cpp int res = 0; // 当前序列长度 for (int i = 1; i <= n; i++) { // 在 dp[1..res] 中找第一个大于 f[i] 的位置 int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; if (pos > res) { dp[++res] = f[i]; // 扩展序列 } else { dp[pos] = f[i]; // 替换为更小的值,保持递增性 } } ``` - **优化效果**:将 LIS 的复杂度从 $O(n^2)$ 降至 $O(n \log n)$[^1][^5]。 #### 4. **与 `lower_bound` 的区别** | 函数 | 返回值条件 | 示例(数组 `[1,3,5]`, `value=3`) | |---------------|--------------------------|-----------------------------------| | `lower_bound` | 第一个 **≥** `value` 的元素 | 返回下标 `2`(元素 `3`) | | `upper_bound` | 第一个 **>** `value` 的元素 | 返回下标 `3`(元素 `5`) | #### 5. **优化建议** 1. **提前判断边界**:若 `f[i]` 大于当前最大值 `dp[res]`,直接扩展序列,避免二分查找: ```cpp if (f[i] > dp[res]) { dp[++res] = f[i]; } else { int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; dp[pos] = f[i]; } ``` 2. **确保数组有序**:`dp[1..res]` 必须严格递增,否则结果错误。 3. **下标从 0 开始的处理**:若数组下标从 `0` 开始,需调整指针范围: ```cpp // 在 dp[0..res-1] 中查找 int pos = upper_bound(dp, dp+res, f[i]) - dp; ``` #### 6. **示例分析** 假设 `dp = [0, 2, 5, 7]`(有效区间 `[1,3]`),`res=3`: - 若 `f[i] = 4`:`upper_bound` 返回 `dp+3`(元素 `5`),下标 `pos = 3`。 - 若 `f[i] = 9`:返回 `dp+4`(越界),`pos = 4`(需扩展序列)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值