Codeforces Round #772 (Div. 2) D. Infinite Set

翻译:

给定一个数组𝑎,该数组由𝑛个不同的正整数组成。

让我们考虑一个无限整数集𝑆,它包含至少满足以下条件之一的所有整数𝑥:

对于某些1≤𝑖≤𝑛,𝑥=𝑎𝑖。
𝑥=2𝑦+1,𝑦在𝑆中。
𝑥=4𝑦,𝑦在𝑆中。
例如,如果𝑎=[1,2],那么𝑆中最小的10个元素将是{1,2,3,4,5,7,8,9,11,12}。

找出𝑆中严格小于2𝑝的元素数量。由于这个数字可能太大,对109+7取模打印。

输入
第一行包含两个整数𝑛和𝑝(1≤𝑛,𝑝≤2⋅105)。

第二行包含𝑛整数𝑎1𝑎2,…,𝑎𝑛(1≤𝑎𝑖≤109)。

可以保证𝑎中的所有数字是不同的。

输出
打印一个整数,即𝑆中严格小于2𝑝的元素数量。记得对109+7取模打印。

例子
inputCopy
2 4
6个1
outputCopy
9
inputCopy
4个7
20 39 5 200
outputCopy
14
inputCopy
2 200000年
48763 1000000000
outputCopy
448201910
请注意
在第一个例子中,小于2^4的元素是{1,3,4,6,7,9,12,13,15}。

在第二个例子中,小于2^7的元素是{5,11,20,23,39,41,44,47,79,80,83,89,92,95}。

思路:

给的小于范围是的是2的幂次方的形式,然后每次变化是2x+1,或者4x,这就很有趣了,我们一可以将给的数组中的数字,转化成2的幂次方,向下取整,每次变换次方+1或者+2,这就变成了类似于经典上楼梯的问题,但是可以会有重复的。这样的话,小的值就会到达大的值,这样的话,如果都计入其对结果的贡献,就会出现有重复的部分,所以我们用map标记去重,然后记录每个的变换次数,类似于可以上多少次台阶,因为每次都是从本身开始遍历,所以相对应的就是从头开始上楼梯。可以仔细想一下,这个写法感觉非常巧妙!!

代码:

#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <math.h>
#include <stdio.h>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<tuple>
#include<numeric>
#include <unordered_map>
using namespace::std;
typedef long long  ll;
inline __int128 read(){
    __int128 x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
inline void print(__int128 x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}
int n,p;
ll dp[200005];
ll ss;
ll mod=1e9+7;
ll ff[200005];
ll a[200005];
map<int, int>now;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(); cout.tie();
    cin>>n>>p;
    int kl=1e6;
    vector<int>we;
    for (int i =1; i<=n; i++) {
        cin>>a[i];
    }
    sort(a+1, a+1+n);
    for (int i=1; i<=n; i++) {
        ll st=a[i];
        int bj=0;
        while (1) {
            if (st==0) {
                break;
            }
            if ((st-1)%2==0) {
                st-=1;
                st/=2;
            }
            else if(st%4==0){
                st/=4;
            }
            else{
                break;
            }
            if (now.count(st)) {
                bj=1;break;
            }
        }
        if (bj) {
            continue;
        }
        now[a[i]]=1;
        int jk=0;
        while (a[i]>0) {
            a[i]/=2;
            jk++;
        }
        we.push_back(jk);
    };
   
    ll na=0;
    dp[1]=1;dp[2]=1;
    
    for (int i =3; i<=p; i++) {
        dp[i]=(dp[i-1]+dp[i-2])%mod;
    }
    for (int i =1; i<=p; i++) {
        ff[i]=(ff[i-1]+dp[i])%mod;
    }
    for(auto x:we){
//        printf("%d ",x);
        if (x>p) {
            continue;
        }
        na=(na+ff[p-x+1])%mod;
//        printf("%lld\n",na);
    }
    printf("%lld\n",na);
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值