杭电多校第九场 Rikka with Mista 基数排序

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6682

题意:

你现在有40个数,每个数不超过44444444(8个4),这些数按照 取/不取 的选择可以有 2 n 2^n 2n 个子集,每个子集都有一个和,问你这些和当中4出现的次数有多少。

做法:

先贴出题解的做法,我觉得他的理论讲的很详细了,我也不需要多讲,写题解重点是要讲如何实现。


我是官方题解
可以对每一位分开来计算,和最大不超过 1 0 10 10^{10} 1010,因此最多只有 10 10 10位。只要对每一位算出它在多少种情况下是 4 4 4 ,全部加起来就可以了。

n n n 40 40 40 的数据范围指明了可以用 m e e t meet meet i n in in m i d d l e middle middle 来做,可以先用 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2) 的复杂度分别把前 n / 2 n/2 n/2 个数和后 n / 2 n/2 n/2 个数的所有和求出来,分别存在数组 A A A B B B 中,那么对于第 i i i 位来说,答案就是有多少个数对 i , j i,j i,j满足 ( A i + B j ) (A_i+B_j) (Ai+Bj) m o d mod mod 1 0 i + 1 ∈ [ 4 × 1 0 i , 5 × 1 0 i ) 10^{i+1} \in [4×10^i,5×10^i) 10i+1[4×10i,5×10i) 。这个问题只要对 A A A B B B 按照 x x x m o d mod mod 1 0 i + 1 10^{i+1} 10i+1排序后用 t w o two two p o i n t e r pointer pointer 扫就可以了。

对每一位分开来做时间复杂度是 O ( 2 n / 2 n l o g w i ) O(2^{n/2}nlogw_i) O(2n/2nlogwi) ,瓶颈在堆每一位排序。不难发现对每一位排序可以用一次归并排序来实现,这样就能省下一个 l o g log log ,时间复杂度是 ( O ( x n / 2 n ) ) (O(x^{n/2}n)) (O(xn/2n))


好了理论结束了,现在就在怎么做了。

首先我们要先把左边和右边的数进行区分,分别用 L , R L,R L,R 来把 n n n 分开存两侧的数字的取和不取的所有状态,当然这里不能简单的就存下数字,为了方便之后我们进行桶排,所以我们这里的 L , R L,R L,R 存的是 p a i r pair pair ,分别代表还未处理的高位数字,和已处理过了的低位数字(在初始状态下低位当然是0)。

然后我们就要进行桶排了,如果不知道什么是桶排的我也…在这里解释起来就是一大堆了…我们用 A [ 10 ] A[10] A[10] B [ 10 ] B[10] B[10] 来分别存我们当前枚举位上的数字,即如果这一位上数字是 x x x ,那么我们就把它存进 A [ x ] A[x] A[x] ,这样我们在找这一位为 4 4 4的时候,只要拿 A [ 0 ] A[0] A[0] & B [ 4 ] B[4] B[4] A [ 1 ] A[1] A[1] & B [ 3 ] B[3] B[3] …来做处理不存在进位的答案即可,当然这些里面我们要存下 L [ i ] . f i r s t / 10 L[i].first/10 L[i].first/10 ,方便对 L L L和R的下一次更新。当然由于存在有进位的情况,所以我们还要拿 A [ 0 ] A[0] A[0] & B [ 3 ] B[3] B[3] A [ 1 ] A[1] A[1] & B [ 2 ] B[2] B[2] … 来作存在进位的情况。

先说不进位的情况,由于我们进行过桶排,所以我们在 A [ i ] , B [ j ] A[i],B[j] A[i],B[j]中的数字已经可以确定都是从小到大有序的,所以我们从小到大枚举 A [ i ] A[i] A[i]中 的每一位,令一个指针从大到小的在 B [ j ] B[j] B[j] 上移动,只要 A [ i ] [ x ] A[i][x] A[i][x]的低位和 + B [ j ] [ p ] B[j][p] B[j][p]的低位和 &lt; 1 0 枚 举 位 x &lt;10^{枚举位x} <10x,那么 a n s + = p + 1 ans+=p+1 ans+=p+1,(因为我们已经保证 B [ j ] B[j] B[j]有序,所以在指针 p p p前面的数也一定满足要求)。

再说进位的情况,情况其实是类似的,但是是从大到小枚举 A [ i ] A[i] A[i]中 的每一位,令一个指针从小到大的在 B [ j ] B[j] B[j] 上移动,只要 A [ i ] [ x ] A[i][x] A[i][x]的低位和 + B [ j ] [ p ] B[j][p] B[j][p]的低位和 ≥ 1 0 枚 举 位 x ≥10^{枚举位x} 10x,那么 a n s + = B [ j ] . s i z e ( ) − p ans+=B[j].size()-p ans+=B[j].size()p,(因为我们已经保证 B [ j ] B[j] B[j]有序,所以在指针 p p p后面的数也一定满足要求)。

为什么要写的那么多那么详细…因为自己太菜了实现困难,所以对着大佬的标程啃了好长一段时间,跟着大概的步骤自己敲了一遍也de了会儿bug…所以想着写详细点…

代码

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=45;

vector<pii> L,R,Sa[10],Sb[10];
int a[maxn],n;
void Insert(int l,int r,ll sum,vector<pii> &T){
    if(r<l){
        T.push_back({sum,0}); return ;
    }
    Insert(l+1,r,sum,T);
    Insert(l+1,r,sum+a[l],T);
}
ll add0(int ia,int ib,ll ba){
    int p=Sb[ib].size()-1; ll ret=0;
    for(int i=0;i<Sa[ia].size();i++){
        pii tp=Sa[ia][i];
        while(p>=0&&Sb[ib][p].se+tp.se>=ba) p--;
        ret+=p+1;
    }
    return ret;
}
ll add1(int ia,int ib,ll ba){
    int p=0,sz=Sb[ib].size(); ll ret=0;
    for(int i=Sa[ia].size()-1;i>=0;i--){
        pii tp=Sa[ia][i];
        while(p<sz&&Sb[ib][p].se+tp.se<ba) p++;
        ret+=sz-p;
    }
    return ret;
}
int main(){
    int T; scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        rep(i,1,n) scanf("%d",&a[i]);
        L.clear(); R.clear();
        Insert(1,(1+n)/2,0,L);
        Insert((1+n)/2+1,n,0,R);
        ll ba=1,ans=0;
        for(int k=0;k<14;k++){
            rep(j,0,9) Sa[j].clear(),Sb[j].clear();

            for(int j=0;j<L.size();j++) Sa[L[j].fi%10].push_back({L[j].fi/10,L[j].se});
            for(int j=0;j<R.size();j++) Sb[R[j].fi%10].push_back({R[j].fi/10,R[j].se});

            for(int i=0;i<10;i++){
                int ad_four=(14-i)%10,ad_thr=(13-i)%10;
                ll ad0=add0(i,ad_four,ba);
                ll ad1=add1(i,ad_thr,ba);
                ans+=ad0+ad1;
            }
            L.clear();R.clear();
            for(int i=0;i<10;i++){
                for(int j=0;j<Sa[i].size();j++){
                    L.push_back({Sa[i][j].fi,Sa[i][j].se+ba*i});
                }
            }

            for(int i=0;i<10;i++){
                for(int j=0;j<Sb[i].size();j++){
                    R.push_back({Sb[i][j].fi,Sb[i][j].se+ba*i});
                }
            }
            ba*=10;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值