【Codeforces】1635D Infinite Set题解

题目大意

给定初始有 n n n个正整数 a i ( 1 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ a i ≤ 1 0 9 ) a_i(1\le n \le 2 * 10^5,1 \le a_i \le 10^9) ai(1n2105,1ai109)集合 S S S
而满足下列条件的数 y y y,也会被添加进这个集合:

  • y = 2 ∗ x + 1 且 x = a i y = 2 * x + 1且x = a_i y=2x+1x=ai
  • y = 4 ∗ x , 且 x = a i y = 4 * x,且x = a_i y=4x,x=ai

问,这个集合最终不超过 2 p ( 1 ≤ p ≤ 2 ∗ 1 0 5 ) 2^p(1 \le p \le 2 * 10^5) 2p(1p2105)的数的个数是多少。

题目链接

思路

我们先假设初始集合中只有 1 1 1。那么 3 , 4 3,4 3,4也会被添加进来,紧接着 12 , 16 , 7 , 9 12,16,7,9 12,16,7,9也会被添加进来。多试几个数我们会发现,不同的操作产生的数不会产生重合

直接设 d p i dp_i dpi表示 i i i以内的数有多少个,那么我们可以轻松得出状态转移方程,但是,显然是开不下的。

我们注意到题目要求不超过 2 p 2^p 2p,所以我们不妨用二进制来表示状态。

f i f_i fi表示有 i i i位二进制位数,其以内的数有多少个。比如 f 4 f_4 f4就表示集合中小于等于 ( 1111 ) 2 (1111)_{2} (1111)2 且大于 ( 1000 ) 2 (1000)_{2} (1000)2的数个数有多少(这么做可以防止重复计算)。

题目中的两种操作在二进制下就是这种操作:

  • x = x < < 1 ∣ 1 x = x << 1 | 1 x=x<<11
  • x = x < < 2 x = x << 2 x=x<<2

我们来写一下状态转移方程 f i f_i fi:

  • 通过 x = x < < 1 ∣ 1 x = x << 1|1 x=x<<11操作,所有 f i − 1 f_{i - 1} fi1中的数都可以转移过来
  • 通过 x = x < < 2 x = x << 2 x=x<<2操作,所有 f i − 2 f_{i - 2} fi2中的数都可以转移过来

故, f i = f i − 1 + f i − 2 f_i = f_{i - 1} + f_{i - 2} fi=fi1+fi2

只有一个的情况我们已经表示出来了,那么假如初始的时候有多个数呢?
假如一个数 x x x可以被另一个数 y y y产生出来,那么我们就不再需要 x x x了。

那么具体怎么做呢?

  • 我们先判断 x x x是否包含在 s e t set set
  • 如果二进制下 x x x的末尾是 1 1 1,那么它可能由 x = x < < 1 ∣ 1 x = x << 1 | 1 x=x<<11操作产生,所以我们令 x > > = 1 x >>= 1 x>>=1
  • 如果二进制下 x x x的末尾是 00 00 00,那么它可能由 x = x < < 2 x = x << 2 x=x<<2操作产生,所以我们令 x > > = 2 x >>= 2 x>>=2
  • 如果 x x x的后两位为 11 11 11,那么就无法继续下去了,结束这种操作。
  • 结束循环后,如果 x x x无法被 s e t set set中的数表示,那就把 x x x加入 s e t set set中。

代码

#include <cstdio>
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;

const int maxN = 2e5 + 7;
const long long Mod = 1e9 + 7;
int n, p, a[maxN], cnt[35];
long long f[maxN];

inline int calc(int x)
{
    int cnt = 0;
    while(x) {
        cnt++;
        x >>= 1;
    }
    return cnt;
}

int main()
{
    scanf("%d%d", &n, &p);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    set<int> num;
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i) {
        if(calc(a[i]) > p)
            continue;
        bool hav = false;
        int x = a[i];
        while(x) {
            if(num.count(x)) {
                hav = true;
                break;
            }
            if(x & 1)
                x >>= 1;
            else if(x & 3)
                break;
            else
                x >>= 2;
        }
        if(!hav)
            num.insert(a[i]);
    }
    for(auto i : num) 
        cnt[calc(i)]++;
    long long ans = 0;
    for(int i = 1; i <= 33; ++i)
        f[i] = cnt[i];
    f[2] += f[1];
    ans = (ans + f[2] + f[1]) % Mod;
    for(int i = 3; i <= p; ++i) {
        f[i] = (f[i] + f[i - 1] + f[i - 2]) % Mod;
        ans = (ans + f[i]) % Mod;
    }
    printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值