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()); }
没有人不辛苦,只有人不喊疼