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次计算,相比最初的暴力可以节省相当多的时间。

总结:

这道题依旧考察位运算的掌握。代码不是重点,但上述思想,如果缺乏对位运算的深刻理解,恐怕很难想到。
当初比赛的时候,我也没想到可以这样转化,纯粹的暴力导致了超时。后来也是询问了做出来的同学,一讲解思路就清晰了不少。

代码大致如下

View Code
 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 }

 

posted @ 2012-07-04 09:34  dgsrz  阅读(723)  评论(1编辑  收藏  举报