UESTC 1709 Binary Operations
题目描述:
Bob有N个数字组成的序列。它们都是如此的吸引人,让Alice乞求获得其中的连续部分(所谓连续部分就是连续子序列)。然而,Bob只允许Alice随机地选择。你能否求出所有选择的数字进行与、或、异或操作之后的期望?
输入:
第一行包含一个整数T(1<=T<=50),表示有T组输入案例。
每个案例的第一行包括一个整数N(1<=N<=50000)
案例第二行有N个整数,表示这个序列。所有整数都落在[0, 10^9]范围内。
输出:
对于每组测试案例,输出“Case #t: a b c”,其中t是从1开始的测试案例的编号,a是与AND运算的期望,b是或OR运算期望,c是异或XOR运算期望。
基本思路:
如果本题数据范围不大,可以直接暴力,即枚举每个序列,分别进行AND\OR\XOR操作,然后求和求期望。但这样明显时间复杂度不够。为此需要进行一些优化。
注意到,对于“A1 A2 A3...”这样的案例,以A1为末尾的子序列有A1,A2为末尾的有A1 A2、A2,A3为末尾的有A3、A2 A3、A1 A2 A3……而以与运算为例,所有子序列位运算之后求和,有:A1 + [(A2&A1) + A2] + [(A3&A2&A1 + A3&A2) + A3] + ……。显然,后一个子序列进行的操作和前一个有关。为此,考虑到数据范围0-10^9,开一个至少30位的数组num[30],存储进行到当前步骤时(即以第i个数字结尾的子序列),每一位上剩余几个1。
初始化这个数组全部为0。然后仍以与运算为例,进行如下操作:
先将这个第i个末尾化为二进制,然后每一位与上一次的状态进行比较。考虑与运算性质,如果这个末尾的二进制表示某一位 j 是0,则结果一定是0,即剩余0个1,故num[j] = 0。若第j位为1,则原来有几个1,与运算之后也有几个1,根据num[j]表示意义,其值不变。
对于或运算,当第j位为1,则num[j]就与执行次数有关(有m个数进行操作,则与运算之后这一位全是1)。假定当前进行到第i个数字,num[j] = i。若第j位为0,num[j]不变。
对于异或,当第j位为1,则num[j]和当前结果这一位有几个0有关。当前结果有几个0,有与num[j](有几个1)和执行次数(总共操作m个数)有关,故num[j]=i-num[j]。若第j位为0,则num[j]不变。
每进行完一次位运算,需要将整个序列的第i个末尾加到状态中(参考上述公式),然后求得当前第i个数为结尾的子序列进行操作后,新值的和。根据num[j]的意义,无非就是将num[j]存储的二进制转为十进制。
最后,求出所有数为结尾的子序列的和S,又因为每种情况都是等可能,求出事件总数 Q=n*(n+1)/2,得E=S/Q。得解。
进行如上优化后,每一位数字只进行30次计算,相比最初的暴力可以节省相当多的时间。
总结:
这道题依旧考察位运算的掌握。代码不是重点,但上述思想,如果缺乏对位运算的深刻理解,恐怕很难想到。
当初比赛的时候,我也没想到可以这样转化,纯粹的暴力导致了超时。后来也是询问了做出来的同学,一讲解思路就清晰了不少。
代码大致如下
1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 using namespace std; 5 typedef long long LL; 6 const int M = 50005; 7 LL num[35], s[M], a[M]; 8 9 void add(LL x) 10 { 11 int k = 0; 12 while (x) { 13 if (x & 1) num[k]++; 14 x >>= 1; 15 k++; 16 } 17 } 18 19 int main() 20 { 21 int t; 22 scanf("%d", &t); 23 for (int x = 1; x <= t; x++) 24 { 25 LL n; 26 double ans1 = 0, ans2 = 0, ans3 = 0; 27 scanf("%lld", &n); 28 printf("Case #%d:", x); 29 double cnt = n * (n + 1) / 2; 30 31 memset(num, 0, sizeof(num)); 32 for (int i = 0; i < n; i++) 33 { 34 scanf("%lld", &a[i]); 35 s[i] = a[i]; 36 if (i == 0) add(a[i]); 37 else 38 { 39 LL x = a[i]; 40 for (int j = 0; j < 31; j++) 41 { 42 if (x & 1) { } 43 else num[j] = 0; 44 s[i] += num[j] * (1 << j); 45 x >>= 1; 46 } 47 add(a[i]); 48 } 49 ans1 += s[i]; 50 } 51 ans1 /= cnt; 52 printf(" %.6lf", ans1); 53 54 memset(num, 0, sizeof(num)); 55 for (int i = 0; i < n; i++) 56 { 57 s[i] = a[i]; 58 if (i == 0) add(a[i]); 59 else 60 { 61 LL x = a[i]; 62 for (int j = 0; j < 31; j++) 63 { 64 if (x & 1) num[j] = i; 65 s[i] += num[j] * (1 << j); 66 x >>= 1; 67 } 68 add(a[i]); 69 } 70 ans2 += s[i]; 71 } 72 ans2 /= cnt; 73 printf(" %.6lf", ans2); 74 75 memset(num, 0, sizeof(num)); 76 for (int i = 0; i < n; i++) 77 { 78 s[i] = a[i]; 79 if (i == 0) add(a[i]); 80 else 81 { 82 LL x = a[i]; 83 for (int j = 0; j < 31; j++) 84 { 85 LL zero = i - num[j]; 86 LL one = num[j]; 87 if (x & 1) num[j] = i - num[j]; 88 s[i] += num[j] * (1 << j); 89 x >>= 1; 90 } 91 add(a[i]); 92 } 93 ans3 += s[i]; 94 } 95 ans3 /= cnt; 96 printf(" %.6lf\n", ans3); 97 } 98 return 0; 99 }