题目链接: 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]的低位和 < 1 0 枚 举 位 x <10^{枚举位x} <10枚举位x,那么 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} ≥10枚举位x,那么 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;
}