2020 China Collegiate Programming Contest - Mianyang Site L. Lottery

题目:L. Lottery
题意:n个盒子,每个盒子有x[i]个 2 a [ i ] 2^{a[i]} 2a[i]的球编号,求每个盒子里取0-x[i]范围内的编号总和有多少个不同的。

带重复的总数很好求,直接累乘,但是去重情况套的很多,没办法下手,需要考虑需要一段是否重叠的二进制串。直接考虑每一个数的贡献,如果当前的数不影响到下一个数,直接乘这一段即可,影响的话找到受影响的一段。
比方说从i开始,类似与多重背包的二进制拆分优化,处理出一段之后,比方说是 2 i 到 2 j 2^i到2^j 2i2j这段区间不会影响下一个区间了,那么他是由 2 i 2^i 2i递推过来的途中所有的数都会被影响,将x拆分为二进制可以组成0-x所有的数,整个一段同除以 2 i 2^i 2i,相当于所有的除以 2 i 2^i 2i的总数,最后隔段相乘得答案。

#include<bits/stdc++.h>
#define mmp make_pair
#define inf 0x3f3f3f3f
#define llinf 0x7fffffffffffffff
using namespace std;
typedef long long ll;
typedef pair<int,int> PP;
typedef double ld;
const ll mod=1e9+7;
const int maxn=1e5+10;
struct A {
    ll num,x;
}a[maxn];
bool cmp(A t1,A t2) {
    return (t1.num<t2.num);
}
ll b[maxn];
int book[maxn];
ll qpow(ll a,ll n) {
    ll res=1;
    while(n) {
        if(n&1) res=(res*a)%mod;
        a=(a*a)%mod;
        n>>=1;
    }
    return res;
}
ll cal(ll n) {
    ll sum=0;
    while(n) {
        n>>=1;
        ++sum;
    }
    return sum;
}
int main() {
    int T;
    scanf("%d",&T);
    int cases=1;
    while(T--) {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;++i) {
            scanf("%lld %lld",&a[i].num,&a[i].x);
        }
        int so=1;
        sort(a+1,a+1+n,cmp);
        for(int i=1;i<=n;++i) {
            b[i]=a[i].x;
        }
        ll ans=1;
        for(int i=1;i<n;++i) {
            ll temp=a[i+1].num-a[i].num;
            ll tn=cal(b[i]);
            if((tn-1)<temp) {
                book[i]=so;
                so++;
                continue;
            }
            book[i]=so;
            b[i+1]+=b[i]/(1 << temp);
        }
        book[n]=so;
      //  for(int i=1;i<=n;++i)
      //      cout<<book[i]<<" ";
       // continue;
        for(int i=1;i<=n;++i) {
            int j=i+1;
            while(j<=n) {
                if(book[j]==book[i]) ++j;
                else break;
            }
            ll sum=a[i].x;
            for(int k=i+1;k<j;++k) {
                ll temp=a[k].num-a[i].num;
                sum=(sum+(a[k].x*qpow(2,temp))%mod)%mod;
            }
            ++sum;
            sum%=mod;
            ans=(ans*sum)%mod;
            i=j-1;
        }
        printf("Case #%d: %lld\n",cases++,ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值