2019杭电暑假多校9:Rikka with Mista【折半+基数排序】

该博客介绍了如何解决2019年杭电暑假多校第9题Rikka with Mista的问题。通过折半搜索策略,结合基数排序的方法,来计算给定数列中所有子集十进制位上4的个数之和。博主详细分析了问题,并给出了去除额外log复杂度的解决方案,利用基数排序在处理低位到高位时统计答案,采用双指针进行计算。

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

题目:

2019杭电暑假多校9:Rikka with Mista

题意:

给定N个数,选一些数相加,一共就有2^N中选法,求每一种选法十进制位上4的个数的和

分析:

看到N <= 40,自然想到了折半搜索,左边搜到了一个值Ai,右边搜到了Bi,然后就算每一位是否会出现4;假设现在考虑Ai+Bi第x位为4,那么有(Ai+Bi)%10^4 属于[4*10^(x-1),5*10^(x-1)),那么就可以把每一位分开计算,把Ai%10^x的值保存在下标为x的桶中并排好序,当右边搜到了Bi时,依次算每一位的贡献,相当于查找下标为x的桶中有多少个数属于[4*10^(x-1),5*10^(x-1)) - Ai%10^x,因为可能产生进位,所以还要查询有多少个数属于[14*10^(x-1),15*10^(x-1)) - Ai%10^x,二分查找即可;当这样会T,因为多了一个log,从低位到高位统计答案时,恰好契合基数排序的过程,所以就一边基数排序一边统计答案,对于排好序的Ai和Bi,用双指针来统计,去掉二分的log,还是看代码吧

代码:

#include <bits/stdc++.h>

#define fi first
#define se second
#define pb push_back
#define pii pair<int,int>
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 50;
int T,n,a[maxn];
vector<pii> A,B,AA[10],BB[10];
void dfs1(int pos,int val){          //爆搜前一半
    if(pos > n/2){
        A.pb(pii(val,0));
        return ;
    }
    dfs1(pos+1,val);
    dfs1(pos+1,val+a[pos]);
}
void dfs2(int pos,int val){         //爆搜后一半
    if(pos <= n/2){
        B.pb(pii(val,0));
        return ;
    }
    dfs2(pos-1,val);
    dfs2(pos-1,val+a[pos]);
}
LL cal(LL a,LL b){                  //双指针统计答案
    int L = 0,R = -1;LL res = 0;
    for(int i = sz(B)-1; ~i; --i){
        while(R+1<sz(A)&&A[R+1].se+B[i].se<b) R++;
        while(L<=R&&A[L].se+B[i].se<a) L++;
        if(A[R].se+B[i].se<b&&A[L].se+B[i].se>=a)
        res += R-L+1;
    }
    return res;
}
LL solve(){
    LL ans = 0,base = 1;
    for(int round = 1;round <= 9; ++round){       //基数排序
        for(int i = 0;i < 10; ++i) AA[i].clear(),BB[i].clear();
        for(int i = 0;i < sz(A); ++i) AA[A[i].fi%10].pb(pii(A[i].fi/10,A[i].se+A[i].fi%10*base));
        for(int i = 0;i < sz(B); ++i) BB[B[i].fi%10].pb(pii(B[i].fi/10,B[i].se+B[i].fi%10*base));
        A.clear(), B.clear();
        for(int i = 0;i < 10; ++i){
            for(int j = 0;j < sz(AA[i]); ++j) A.pb(AA[i][j]);
            for(int j = 0;j < sz(BB[i]); ++j) B.pb(BB[i][j]);
        }   
        ans += cal(4*base,5*base) + cal(14*base,15*base);
        base *= 10;
    }
    return ans;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n); A.clear(), B.clear();
        for(int i = 1;i <= n; ++i) scanf("%d",a+i);
        dfs1(1,0); dfs2(n,0);
        printf("%I64d\n",solve());
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值