#G.石老板含笑九泉 sol-基数排序,meet in the middle

发布时间 2023-11-24 21:11:13作者: H_W_Y

#G.石老板含笑九泉 sol-基数排序,meet in the middle

数字 \(4\) 代表着一种邪恶力量,现在定义一个团队的邪恶力量为他们罪恶程度之和的十进制表示中 \(4\) 的个数。

那么问题来了,在这 \(n\) 个人的所有 \(2^n\) 个子集中,邪恶力量之和为多少。

\(1 \le n \le 40,1 \le a_i \le 5e8\)

看到数据范围,很容易想到是 meet in the middle,但是一直卡在不能排序那里,时限是 \(1s\)

发现每一次都是按照每一位的顺序去排序的,所以复杂度才那么大。

那么真的之前的排序就没有意义了吗?

我们考虑 SA 后缀排序时所用到的基数排序——

发现这几乎是一模一样的,于是把排序过程改成基数排序就可以了——这下这下了。

这道题也是基数排序的罕见运用吧。

代码是好写的。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back

int n,v[55],t[1<<20],e[1<<20];
ll ans=0;
vector<int> a,b;

void init(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>v[i];
  for(int s=0,p=0;s<(1<<(n/2));s++,a.pb(p),p=0) for(int i=1;i<=n/2;i++) if(s&(1<<(i-1))) p+=v[i]; 
  for(int s=0,p=0;s<(1<<(n-n/2));s++,b.pb(p),p=0) for(int i=n/2+1;i<=n;i++) if(s&(1<<(i-n/2-1))) p+=v[i];
}

void wrk(int s,vector<int> &a){
  int c[10]={0},n=a.size();
  for(int i=0;i<n;i++) c[e[i]=a[i]/s%10]++;
  for(int i=1;i<10;i++) c[i]+=c[i-1];
  for(int i=n-1;i>=0;i--) t[--c[e[i]]]=a[i];
  for(int i=0;i<n;i++) a[i]=t[i];
}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  init();
  for(int p=0,s=1;p<9;p++){
  	wrk(s,a);wrk(s,b);
  	int mod=s*10;
  	for(int i=0;i<(int)a.size();i++) e[i]=a[i]%mod;
  	for(int i=0;i<(int)b.size();i++) t[i]=b[i]%mod;
  	int l=0,r=0;
  	for(int i=(int)b.size()-1;i>=0;i--){
	  while(l<(int)a.size()&&e[l]+t[i]<4*s) l++;
	  while(r<(int)a.size()&&e[r]+t[i]<5*s) r++;
	  ans+=1ll*(r-l); 
	}
	l=r=0;
	for(int i=(int)b.size()-1;i>=0;i--){
	  while(l<(int)a.size()&&e[l]+t[i]<14*s) l++;
	  while(r<(int)a.size()&&e[r]+t[i]<15*s) r++;
	  ans+=1ll*(r-l);
	}
	s*=10;
  }
  printf("%lld\n",ans);
  return 0;
}

Conclusion

对于一部分一部分的排序我们可以考虑用基数排序去加速。