TOJ--1114--rmq/线段树

嗯 表示rmq真的好伤我......

这题是 大神 噢 不对 那他的称呼和晓爷冲突了..

他的做法 真的很好.... 时间在toj 又是最短的...

关于 rmq  我想说 理解它 真的很有用....

我又要重新好好学一遍了..

下面附上他的代码:----内含详细注释  不清楚的 可以再下方留言告诉我 基本每天总会上号的---------

 

  1 #include <stdio.h>
  2 
  3 int counts[100000]; // 每组数据的重复出现的次数
  4 int boundary[100000]; // 记录下相同数据第一次出现的下标
  5 int num[100000];  // n个数据的值
  6 int hash[100000];  // 数据所对应的编号
  7 int maximum[100000][18];
  8 
  9 int main() {
 10     int n, q, i, j, gnum, width, half, left, right, ans, freq, index;
 11     // 读取个数n
 12     while (scanf("%d", &n) && n) 
 13     {
 14         // 读取询问数q
 15         scanf("%d", &q);
 16         for (i = 0; i < n; i++) 
 17         {
 18             // 读取num[i]
 19             scanf("%d", num + i);
 20         }
 21         // gnum代表不同的数字的分组的总数
 22         gnum = 0;
 23         // 扫一遍每一个数
 24         for (i = 0; i < n; i++) 
 25         {
 26             // 如果这个是第一个数,或者这个数和前面一个不一样
 27             if (i == 0 || num[i] != num[i - 1]) 
 28             {
 29                 // 添加一个分组,并且那个分组的计数为0
 30                 counts[gnum++] = 0;
 31                 // boundary[i] 表示的是跟第i个数所在的组的第一个数的下标
 32                 // 因为i是所在组的第一个数,所以boundary[i] = i
 33                 boundary[i] = i;
 34             }
 35             else 
 36             { 
 37                 // 因为这个数和前面的数一样,所以boundary[i] 和左边的 boundary是一样的
 38                 boundary[i] = boundary[i - 1];
 39             }
 40             // hash[i] 表示 当前的数的分组编号是gnum - 1
 41             hash[i] = gnum - 1;
 42             // 并且为那个分组的数目加上1个,这就是为什么刚刚counts要设置为0,在这里还会加上的   
 43             counts[gnum - 1]++;
 44         }
 45         // 下面的部分是展示如何正确使用rmq (Range maximum/minimum query)
 46         // 首先给出maximum[i][j]的定义
 47         // maximum[i][j] 等于下标从i开始往后面数2^j那么多元素中的最大的那个
 48         for (i = 0; i < gnum; i++) 
 49         {
 50             // 因为j=0,所以maximum[i][0]就是只有1个元素counts[i] 
 51             maximum[i][0] = counts[i];
 52         }
 53         // 从1开始枚举j,直到2^j已经超过counts数组大小为止
 54         for (j = 1; (1 << j) <= gnum; j++) 
 55         {
 56             // 考虑的连续的个数是2 ^ j
 57             width = 1 << j;
 58             // 个数的一半是 half
 59             half = width >> 1;
 60             // 从起点i=0开始枚举,直到从i开始到数组的结尾已经不足width那么多个数为止
 61             for (i = 0; i + width <= gnum; i++) 
 62             {
 63                 // 从i到i+half-1的最大值是left
 64                 left = maximum[i][j - 1];
 65                 // 从i+half到i+width-1的最大值是right
 66                 right = maximum[i + half][j - 1];
 67                 // 那么从i到i+width-1的最大值是left和right中比较大的那个
 68                 maximum[i][j] = left > right ? left : right;
 69             }
 70         }
 71         // q个询问
 72         while (q--) 
 73         { 
 74             // 读取左右
 75             scanf("%d%d", &left, &right);
 76             // 换成 0 下标
 77             left--;
 78             right--;
 79             // 如果左右的hash一样,说明中间的数都是同一个
 80             if (hash[left] == hash[right])   // hash值相同 证明在同一个分组 即数据相同
 81             {
 82                 // 答案就是区间长度
 83                 printf("%d\n", right - left + 1);
 84                 continue;
 85             }
 86             // freq 是我们要记录的最大频次
 87             // 对于 left 来说,我们并不能取的所有的数,所以我们快捷的计算一下left的个数
 88             // left的个数等于left所在的组的总个数 + left离改组第一个数的距离
 89             // 这个可能有点难想象,不过把端点情况枚举一下就可以知道了
 90             // 当left和boundary[left]一样的时候,个数就是counts[hash[left]]
 91             // 随着left增大,个数递减,所以left前面是负号
 92             freq = counts[hash[left]] + boundary[left] - left;// 其实这个式子还是蛮容易看懂的....
 93             // right所在的个数,如果right和boundary[right]一样,那么就只有1个
 94             // 因为随着right递增,个数增加,所以right前面是正号
 95             if (right - boundary[right] + 1 > freq) //虽然和上面求left的个数有点区别 但还是手动模拟就可以知道
 96             {
 97                 // 如果right的freq比较大,更新freq
 98                 freq = right - boundary[right] + 1;
 99             }
100             // left 替换为left所在组下一个组的组坐标
101             left = hash[left] + 1; // 
102             // right 替换为right所在组的前一个组的坐标
103             right = hash[right] - 1;
104             // left <= right 就是说中间还有别的组
105             if (left <= right) {
106             
107                 // 中间组的个数是width
108                 width = right - left + 1;
109                 
110                 // 现在我们要查找的就是counts[left]到counts[right]中的最大值
111                 // 这里有问题吗,要查询的东西
112                 j = 0;
113                 // 这里我们需要一个j, 使得2^j的两倍不少于width,且2^j不大于width
114                 //所以只要2^(j+1) < width, 我们就增加j
115                 while ( (1 << (j + 1) ) < width ) j++;
116                 // int j = (int)(log(v-s+1.0)/log(2.0)); 这样也可以 但是上面的更直观
117                 // 这里查询的是 counts[left] 到 counts[left + 2^j - 1] 中的最大值
118                 index = maximum[left][j];
119                 if (freq < index ) 
120                 {
121                     freq = index;
122                 }
123                 // 这里查询的是counts[right - 2^j + 1] 到 counts[right] 中的最大值
124                 index = maximum[right - (1 << j) + 1][j];
125                 if (freq < index) 
126                 {
127                     freq = index;
128                 }
129                 // 到这里你应该明白rmq是怎么一回事了
130                 // 上面的区间有这样一个特性
131                 // 第一个区间是从left开始,长度为2^j, 第二个区间以right结束,长度为2^j
132                 // 因为长度是2^j, 所以我们之前都已经算好了,这里直接拿来用
133                 // 而且因为2^j的两倍,至少有width那么大,所以必定能覆盖left到right的所有元素
134                 // 而且2^j 不超过width,所以也不用担心会超过边界
135                 // 左右两个长度为2^j的分区的最大值中的最大值,则必定是整个区间的最大值
136             }
137             printf("%d\n", freq);
138         }
139     }
140     return 0;
141 }
View Code

 

today:

  我说了所有的谎 你全都相信

  简单的我爱你 你却老不信

 

posted @ 2014-06-12 09:58  radical  阅读(163)  评论(0编辑  收藏  举报