Contest Hunter 3801 Rainbow的信號

題面

題意

給出nn個數,隨機rand兩個1~n的數ll,rr,若l>rl>r,則交換兩數,求區間[l,r][l,r]中的數異或,與,或的值的期望。

做法

首先因爲是位運算,因此我們可以逐位考慮,對每一位單獨計算,下面考慮第u+1u+1位:
可以發現長度爲1的區間被rand到的概率均爲1/n/n1/n/n,長度大於1的區間被rand到的概率均爲2/n/n2/n/n,因此我們要分開來處理。
對於長度爲1的區間,很好處理,如果這位是1,對三個答案都加上(1<<u)1/n/n(1 << u)*1/n/n,反之不對答案做出任何貢獻。
對於長度大於1的區間,我們可以枚舉右端點,並記錄0和1上一次出現的位置,然後計算三個答案:
1.與:只有連續的一段1才行,因此答案爲(ipos[0]num[i])2/n/n(i-pos[0]-num[i])*2/n/n,注意要減去長度爲1的區間的貢獻。
2.或:只要區間中有1即可,因此答案爲(pos[1]num[i])2/n/n(pos[1]-num[i])*2/n/n
3.異或:可以發現合法的左端點是相間的區間,每個區間有一些0(可以沒有)和一個1組成,因此用兩個變量分別維護這兩種區間的數的個數,然後每次計算貢獻即可,具體可以參考代碼。

代碼

#include<iostream>
#include<cstdio>
#define db double
#define N 100100
using namespace std;

int n,num[N],tmp[N];
db an1,an2,an3;

inline void solve(int u)
{
	int i,j,pos[2],cnt[2];
	bool now=0;
	db t;
	for(i=1;i<=n;i++)
	{
		if(num[i]&(1 << u))
		{
			tmp[i]=1;
			an1+=(1 << u)*1./n/n;
			an2+=(1 << u)*1./n/n;
			an3+=(1 << u)*1./n/n;
		}
		else tmp[i]=0;
	}
	cnt[0]=cnt[1]=pos[0]=pos[1]=0;
	for(i=1;i<=n;i++)
	{
		pos[tmp[i]]=i;
		cnt[now]++;
		an1+=(1 << u)*2./n/n*(cnt[now^(!tmp[i])]-tmp[i]);
		an2+=(1 << u)*2./n/n*(i-pos[0]-tmp[i]);
		an3+=(1 << u)*2./n/n*(pos[1]-tmp[i]);
		now^=tmp[i];
	}
}

int main()
{
	int i,j;
	cin>>n;
	for(i=1;i<=n;i++) scanf("%d",&num[i]);
	for(i=0;i<30;i++) solve(i);
	printf("%.3f %.3f %.3f",an1,an2,an3);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章