Frequent Values-线段树求解出现最多的数
Frequent Values(poj 3368)
注意:以下答案为离线作答结果,并非能通过poj,若要通过poj,需要修改函数接口,因为以下程序接受半封闭区间(s,e],同时还需要修改输入数据的顺序
求出现最多的数:
给定n个数,已按从大到小顺序排列好,一共有q个询问,每次询问一个区间,问这个区间中出现次数最多的数是什么。
题目数据范围:
数的个数,1 ≤ n ≤ 100000
询问次数,1 ≤ q ≤ 100000
每个数的大小,-100000 ≤ ai ≤ 100000
思路:
使用线段树解决。因个数比较多,最多有10w个数据,又因为输入是有序的,从大到小输入。因此可以将相同数据保存到同一个结构体中,并记录一共有多少个相同的元素。这样做既可以免去合并子树求解出现最多数的麻烦,也节约了空间,提高了效率。
题目中使用数组来模拟线段树。
1 /********************************************************** 2 作者:SunboyL 3 题目3:Frequent Values(poj 3368) 4 5 求出现最多的数: 6 给定n个数,已按从大到小顺序排列好,一共有q个询问,每次询问一个区间,问这个区间中出现次数最多的数是什么。 7 题目数据范围: 8 数的个数,1 ≤ n ≤ 100000 9 询问次数,1 ≤ q ≤ 100000 10 每个数的大小,-100000 ≤ ai ≤ 100000 11 12 13 思路:很容易想到建立线段树,并在线段树的每个结点中保存区间中出现次数最多的数。 14 需要解决的问题,两个子结点的信息如何合并到父结点。 15 很显然,子结点中出现次数最多的数不一定就是父结点中出现次数最多的数 16 17 时间:2013.10.28 18 **********************************************************/ 19 20 #include <vector> 21 #include <algorithm> 22 #define MAXN (100000) 23 24 typedef struct _dataSet 25 {// 一个值相同的数据集合 26 int value,count,lastNumSeq; //value表示一个值;count表示有多少个value值,seq表示在线性表中该集合最后一个值的序号 27 int setSeq;// 集合序号,表示第几个集合 28 }DataSet; 29 30 typedef struct _segTree 31 { 32 int s,e; // 表示区段[s,e) 33 DataSet mostValue; // mostValue表示区段[s,e)中出现最多的值. 34 }SegTree,*pSegTree; 35 36 void buildSegTree(pSegTree tree,int start,int end,int node) 37 { // tree为树根节点,start到end区间为[start,end),node表示节点编号 38 tree[node].s = start; 39 tree[node].e = end; 40 tree[node].mostValue.value = 0x80000000;//0x80000000为最小的32位整数 41 if(start + 1 == end) 42 return; 43 int mid = (start + end) >> 1; 44 buildSegTree(tree,start,mid,node*2); // 建立左子树 45 buildSegTree(tree,mid,end,node*2+1); // 建立右子树 46 } 47 48 void insertDataSet(pSegTree segTree,const DataSet& dataSet,int node = 1) 49 {// 将dataSet插入到segTree中,node默认指向根节点,调用时忽略此参数 50 if(dataSet.count > segTree[node].mostValue.count) 51 segTree[node].mostValue = dataSet;// 每个节点都记录区间中出现次数最多的数 52 if(segTree[node].s + 1 == segTree[node].e) 53 return;// 叶子节点 54 int mid = (segTree[node].s + segTree[node].e) / 2; 55 if(dataSet.setSeq < mid) // 线段树以集合的个数来划分线段,因此使用集合所在的序号判断插入到左子树或是右子树 56 insertDataSet(segTree,dataSet,node*2); 57 else 58 insertDataSet(segTree,dataSet,node*2+1); 59 } 60 61 DataSet query(pSegTree tree,const std::vector<DataSet>& dataSet,int l,int r,int node = 1) 62 {// 查询在线性列表dataSet[]中(l,r]区间出现次数最多的数,并返回该数的相同元素集合 63 DataSet result; 64 int mid = (tree[node].s + tree[node].e) / 2; 65 int ds,de; // 将以集合为单位的区间转换为以单个数据值为单位的区间[ds,de) 66 if(tree[node].s == 1) 67 ds = 1; 68 else 69 ds = dataSet[tree[node].s - 2].lastNumSeq + 1; 70 de = dataSet[tree[node].e - 2].lastNumSeq + 1; // 因为std::vector<DataSet>& dataSet数组从0开始,所以相对起始位置,应该是减2 71 72 if(l == ds && r == de) 73 { 74 return tree[node].mostValue; 75 } 76 if(tree[node].s + 1 == tree[node].e) 77 { 78 result = tree[node].mostValue; 79 result.count = r - l;// 区间并不完全覆盖相同元素的集合,则相同的个数为其部分覆盖的个数。 80 return result; 81 } 82 DataSet a,b; 83 a.count = b.count = 0; 84 if(l <= dataSet[mid - 2].lastNumSeq) 85 a = query(tree,dataSet,l,std::min(r,dataSet[mid - 2].lastNumSeq + 1),node*2); 86 if(r > dataSet[mid - 2].lastNumSeq + 1) 87 b = query(tree,dataSet,std::max(l,dataSet[mid - 2].lastNumSeq + 1),r,node*2+1); 88 result = a.count>b.count?a:b; 89 return result; 90 } 91 92 void frequentValues() 93 {// frequentValues函数是本程序接口。负责输入。 94 int n,i; // n:有多少个要输入的元素 95 std::vector<DataSet> dataSet; // 本题目主要研究线段树而非线性表,这里就使用一下容器偷懒 96 DataSet temp; 97 98 std::cin >> n; // 输入一共有多少个数据 99 if(n < 1 || n > MAXN) // 元素个数为1到100000 100 return; 101 102 std::cin >> temp.value;// 输入第一个元素的值 103 temp.count = 1; 104 temp.lastNumSeq = 1; 105 temp.setSeq = 1;// 第一个集合 106 dataSet.push_back(temp); 107 int q = 0; // 指向dataSet数组中当前值(集合)的指针 108 109 for( i = 2;i <= n;++i ) 110 { 111 std::cin >> temp.value; 112 if(temp.value == dataSet[q].value) 113 { 114 dataSet[q].count ++; 115 dataSet[q].lastNumSeq ++; 116 } 117 else 118 { 119 temp.count = 1; // 值不相同,表示另外一个集合,充值value值有一个,序号为前一个集合的最后一个值的序号加一 120 temp.lastNumSeq = dataSet[q].lastNumSeq + 1; 121 temp.setSeq = dataSet[q++].setSeq + 1; 122 dataSet.push_back(temp); 123 } 124 } 125 int size = dataSet.size(); // 记录值不同的集合有多少个,并利用这个,建立1-size的线段树 126 pSegTree segTree = new SegTree[4*size]; 127 128 buildSegTree(segTree,1,size+1,1); // 以segTree为根节点,集合个数size(1-size+1)为线段区间建立线段树 129 for(i = 0;i < size;++i) 130 insertDataSet(segTree,dataSet[i]);// 将dataSet中的数据集合插入到线段树中 131 132 int ask; // 多少次询问 133 std::cin >> ask; 134 while(ask --) 135 { 136 DataSet result; 137 int l,r; 138 std::cin >> l >> r;// 输入要询问的区间[l,r) 139 result = query(segTree,dataSet,l,r); 140 int t = result.value; 141 std::cout << t << std::endl; 142 } 143 delete[] segTree; 144 145 } 146 147 #include <iostream> 148 #include <list> 149 using namespace std; 150 int main() 151 { 152 //MergeSortTest(); // 测试题目一,归并排序 153 //movableWindowsTest();// 测试题目二,移动窗口,输出窗口中的最小值 154 //MonkeyKingTest();// 测试题目三,猴王monkey king 155 frequentValues(); // 测试题目四,Frequently Values 156 157 return 0; 158 }
20
50 48 48 48 48 47 39 39 39 39 20 20 17 16 16 16 16 16 12 11
5
1 21
3 12
5 9
7 17
7 18
输出结果如下: