AcWing216 Rainbow的信号 (期望)

这道题要求的是贡献的平均值。

直接计算挺难的,但是我们可以考虑按位枚举贡献,因为最多就30位,现在我们要知道的是,什么是贡献

首先一共有n*n种取法,也就是最后求出来的数要除以n方

那么我们分别来看三个函数的分子是什么,对于每一位,我们都枚举每个数当作右端点,之后寻找合法左端点就行了,这是常用的枚举所有情况的方法。

因为我们现在已经拆分到位了,对于and,我们发现,如果右端点是0,那么没有答案,否则就是找到最近的一个0,在中间的都是合法的

对于or来说,只要当前为是1,那么都是合法的,否则找到最近的1,在这之前的左端点都是合法的

对于xor来说,我们要维护前缀和,只要和当前前缀和不同的,都是答案。

注意的是,这个答案要*2,因为我们我们可以随意取左右端点,当左大于右的时候,翻转过来跟我们上面求的是一样的。

但是有一点,如果左右端点相同,那只有一次,因此要先减去这种情况,之后*2,之后再把这种情况加回来

还有一点,n*n爆int

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N][31];
int s[N];
long long n;
double sxor(){
    double ans=0;
    int i,j;
    for(j=0;j<=30;j++){
        memset(s,0,sizeof s);
        int c[2]={1,0};
        for(i=1;i<=n;i++){
            s[i]=s[i-1]+b[i][j];
            ans+=(1ll<<j)*c[s[i]%2==0];
            c[s[i]%2]++;
        }
    }
    for(i=1;i<=n;i++)
    ans-=a[i];
    ans*=2;
    for(i=1;i<=n;i++)
    ans+=a[i];
    return ans/(n*n);
}
double sand(){
    double ans=0;
    int i,j;
    for(j=0;j<=30;j++){
        int last=0;
        for(i=1;i<=n;i++){
            if(b[i][j]){
                ans+=(1ll<<j)*(i-last);
            }
            else{
                last=i;
            }
        }
    }
    for(i=1;i<=n;i++)
    ans-=a[i];
    ans*=2;
    for(i=1;i<=n;i++)
    ans+=a[i];
    return ans/(n*n);
}
double sor(){
    double ans=0;
    int i,j;
    for(j=0;j<=30;j++){
        int last=0;
        for(i=1;i<=n;i++){
            if(b[i][j]){
                ans+=(1ll<<j)*i;
                last=i;
            }
            else{
                ans+=(1ll<<j)*(last);
            }
        }
    }
    for(i=1;i<=n;i++)
    ans-=a[i];
    ans*=2;
    for(i=1;i<=n;i++)
    ans+=a[i];
    return ans/(n*n);
}
int main(){
    cin>>n;
    int i,j;
    for(i=1;i<=n;i++){
        cin>>a[i];
        int x=a[i];
        for(j=0;j<=30;j++){
            if((x>>j)&1)
            b[i][j]=1;
        }
    }
    printf("%.3f %.3f %.3f\n",sxor(),sand(),sor());
}
View Code

 

posted @ 2020-04-15 20:10  朝暮不思  阅读(189)  评论(0编辑  收藏  举报