原创题,没有提交的地方。
一、题目
众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:
勇太有一个长度为 n 的数列,现在六花可以从中选出若干个数(不可以不取)。她的方案需要满足她选出的所有数的平均数小于等于中位数。现在勇太想让六花求出满足条件的方案数。
注:数列长度为偶数的时候,中位数为排序后最中间的两个数的平均数。
当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?
二、解法
先把所有数从小到大排序,接着可以枚举中位数是哪两个数的平均数,假设是l和r(长度为奇数的情况相当于l=r),然后我们给数列中所有数减去平均数,那么这时的方案数就等于从[1,l),(r,n]这两个区间中选出相同数目的数使得它们的和小于0。
我们可以把待选的数给单独拿出来(即把区间[l,r]的数删掉),如果原来它的下标小于l,把么权重为1否则权重为-1。问题就相当于给出若干个数,你要选出一些数使得它们的权重和为0且权值和小于0,这是一个经典的meet in middle的问题。我们可以把数均等的分成两部分,分别处理出两部分中的所有选取方法,然后根据权重分组,每组排序之后就能统计答案了。
上述算法的时间复杂度是∑i=1n(n−i)∗2(n−i)/2∗(n−i)/2=O(2n/2∗n2)\sum_{i=1}^n (n-i)*2^{(n-i)/2}*(n-i)/2=O(2^{n/2}*n^2)∑i=1n(n−i)∗2(n−i)/2∗(n−i)/2=O(2n/2∗n2),这样是会超时的(除非你的常数足够好)。
我们发现我们没有必要排序,我们可以在枚Q举的时候使用归并的方法,直接让每组有序,这样就能减少一个n的复杂度。
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int w[50],x[50];
int A[50],n,N,li;
vector<long long>L[50],R[50],t;
void solve1(int lim){
L[0].push_back(0);
for (int now=1;now<=lim;now++)
for (int i=min(li,now);i;i--){
t=L[i]; L[i].clear(); int l=0,r=0;
while (l<L[i-1].size()&&r<t.size())
if (L[i-1][l]+x[now]<=t[r]){
L[i].push_back(L[i-1][l]+x[now]); l++;
} else {
L[i].push_back(t[r]); r++;
}
while (l<L[i-1].size()) L[i].push_back(L[i-1][l]+x[now]),l++;
while (r<t.size()) L[i].push_back(t[r]),r++;
}
}
void solve2(int lim){
R[0].push_back(0); int now=0; li=0;
for (int kk=n;kk>lim;kk--){
if (w[kk]==-1){
now++; li=max(li,now);
for (int i=now;i;i--){
t=R[i]; R[i].clear();
vector<long long>::iterator l=R[i-1].begin(),r=t.begin();
while (l!=R[i-1].end()&&r!=t.end())
if ((*l)+x[kk]<=(*r)){
R[i].push_back((*l)+x[kk]); l++;
} else {
R[i].push_back((*r)); r++;
}
while (l!=R[i-1].end()) R[i].push_back((*l)+x[kk]),l++;
while (r!=t.end()) R[i].push_back((*r)),r++;
}
} else
for (int i=0;i<now;i++){
t=R[i]; R[i].clear(); int l=0,r=0;
while (l<R[i+1].size()&&r<t.size())
if (R[i+1][l]+x[kk]<=t[r]){
R[i].push_back(R[i+1][l]+x[kk]); l++;
} else {
R[i].push_back(t[r]); r++;
}
while (l<R[i+1].size()) R[i].push_back(R[i+1][l]+x[kk]),l++;
while (r<t.size()) R[i].push_back(t[r]),r++;
}
}
}
long long solve(){
int mid=n/2;
for (int i=0;i<=n;i++) L[i].clear();
for (int i=0;i<=n;i++) R[i].clear();
solve2(mid);
solve1(mid);
long long ans=0;
for (int now=0;now<=li;now++){
int k1=R[now].size()-1;
for (int i=0;i<L[now].size()&&k1>=0;i++){
while (k1>=0&&R[now][k1]+L[now][i]>0) k1--;
ans+=k1+1;
}
}
return ans;
}
int main(){
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
scanf("%d",&N);
for (int i=1;i<=N;i++) scanf("%d",&A[i]);
for (int i=1;i<=N;i++) A[i]*=2;
sort(A+1,A+N+1);
long long ans=0;
for (int l=1;l<=N;l++)
for (int r=l;r<=N;r++){
n=0; int mi=(A[l]+A[r])/2;
li=min(li,min(l-1,N-r));
if (l-1>=N-r){
for (int i=1;i<l;i++){
w[++n]=1; x[n]=A[i]-mi;
}
for (int i=r+1;i<=N;i++){
w[++n]=-1; x[n]=A[i]-mi;
}
} else {
for (int i=r+1;i<=N;i++){
w[++n]=1; x[n]=A[i]-mi;
}
for (int i=1;i<l;i++){
w[++n]=-1; x[n]=A[i]-mi;
}
}
ans+=solve();
}
printf("%lld\n",ans);
return 0;
}