Problem A: [noip2016十连测第三场]平均数
Time Limit: 10 Sec
Memory Limit: 256 MB
Submit: 158
Solved: 49
[
Submit][
Status][
Web Board]
Description
有一天,小A得到了一个长度为n的序列。他把这个序列的所有连续子序列都列了出来,并对每一个子序列都求了其
平均值,然后他把这些平均值写在纸上,并对它们进行排序,最后他报出了第k小的平均值。你要做的就是模仿他
的过程。
Input
第一行两个整数n,k,意义如题中所述。
第二行n个正整数,即为小A得到的序列。
Output
一行一个实数,表示第k小的平均值,保留到小数点后4位。
【 数据范围与约定】
对于 40%的数据, n≤1000
对于 100%的数据, n≤100000, k≤n*(n+1)/2, 序列中的数≤10^9
Sample Input
<span class="sampledata">6 10
3 5 4 6 1 2</span>
Sample Output
<span class="sampledata">3.6667</span>
HINT
[
Submit][
Status]
题解:实数二分+树状数组+排序。
先分析一下题目,如果是暴利枚举的话肯定会TLE。所以我们只能考虑二分答案,我们二分到一个平均数,如何判断他是否可行呢?加上当前的平均数为x,那么我们对于每一个数都减去x,对于每一个位置求前缀和,然后将得到的前缀和排序。通过前缀和作差我们是可以得到一段区间的总和的,我们要求平均数<=x的子序列个数,那么在所有数减去x的情况下就是求有多少区间的和是<=esp(精度误差),那么我们将前缀和按照从大到小排序,当前位置之前有多少在数列中位置小于当前位置的数就是对答案的贡献,可以用树状数组来统计答案。但是这样做在不开O2的情况下会TLE。
[cpp] view plain copy
- #include<iostream>
- #include<cstdio>
- #include<algorithm>
- #include<cstring>
- #include<cmath>
- #define N 100003
- #define esp 1e-7
- #define LL long long
- using namespace std;
- int n;
- LL m,tr[N];
- double b[N],a[N],maxn,minn,l,r;
- struct data
- {
- double x;
- int pos;
- }sum[N];
- int cmp(data a,data b)
- {
- return a.x-b.x>esp||a.x-b.x<=esp&&a.x-b.x>=-esp&&a.pos<b.pos;
- }
- int lowbit(int x)
- {
- return x&(-x);
- }
- void change(int x)
- {
- for (int i=x;i<=n;i+=lowbit(i))
- tr[i]++;
- }
- LL sum1(int x)
- {
- LL ans=0;
- for (int i=x;i>=1;i-=lowbit(i))
- ans+=tr[i];
- return ans;
- }
- bool pd(double x)
- {
- sum[0].x=0; sum[0].pos=0;
- for (int i=1;i<=n;i++) sum[i].x=sum[i-1].x+a[i]-x,sum[i].pos=i,tr[i]=0;
- sort(sum+1,sum+n+1,cmp);
- change(sum[1].pos); LL t=0;
- if (sum[1].x<=esp) t++;
- for (int i=2;i<=n;i++)
- {
- if (sum[i].x<=esp) t++;
- t+=sum1(sum[i].pos);
- change(sum[i].pos);
- }
- if (t>=m) return true;
- return false;
- }
- int main()
- {
- freopen("a.in","r",stdin);
-
- scanf("%d%I64d",&n,&m); l=1e9;
- for (int i=1;i<=n;i++) scanf("%lf",&a[i]),r=max(r,a[i]),l=min(l,a[i]);
- double ans=1e9;
- while (r-l>=esp)
- {
- double mid=(l+r)/2;
- if (pd(mid)) ans=min(ans,mid),r=mid;
- else l=mid;
- }
- printf("%.4lf\n",ans);
- }
题解:实数二分+归并排序
上一种做法常数比较大,因为排序+树状数组相当于是两倍的常数。
其实排序和树状数组就是为了求逆序对数,我们可以利用归并排序直接求,这样就只有一倍的常数,就可以过了。
[cpp] view plain copy
- #include <cstdio>
- #include <iostream>
- #include <cstdlib>
- #include <cstring>
- #include <algorithm>
- #include <cmath>
- #include <queue>
- using namespace std;
- typedef double db;
- typedef long long ll;
- const int N=100010;
- const db eps=1e-6;
- db b[N],c[N];
- int a[N];
- ll ans=0;
- int n;
- void solve(int l,int r){
- if(l==r) return;
- int M=l+r>>1;
- solve(l,M);solve(M+1,r);
- int i=l,j=M+1,k=l-1;
- while(i<=M&&j<=r){
- if(b[i]<b[j]) c[++k]=b[i++];
- else c[++k]=b[j++],ans+=M-i+1;
- }
- while(i<=M) c[++k]=b[i++];
- while(j<=r) c[++k]=b[j++];
- for(i=l;i<=r;i++) b[i]=c[i];
- }
- ll calc(db x){
- b[0]=0;
- for(int i=1;i<=n;i++) b[i]=a[i]-x+b[i-1];
- ans=0;
- solve(0,n);
- return ans;
- }
- int main(){
- freopen("ave.in","r",stdin);
- freopen("ave.out","w",stdout);
- int mx=0,i;
- db lb,rb,mid;
- ll x;
- scanf("%d %lld",&n,&x);
- for(i=1;i<=n;i++) scanf("%d",&a[i]),mx=max(mx,a[i]);
- lb=0;rb=mx;
- while(rb-lb>eps){
- mid=(lb+rb)/2;
- if(calc(mid)<x) lb=mid;
- else rb=mid;
- }
- printf("%.4lf\n",lb);
- return 0;
- }
-