信息学奥赛一本通 1239:统计数字(禁STL及相关调用)
【题目链接】
ybt 1239:统计数字
注意:本题目禁止使用STL及包含可以使用的相关调用
一本通中限制不许使用STL,那么引入头文件不能写<bits/stdc++.h>
,只能写<iostream>
,否则不允许提交。
【题目考点】
1. 二分查找
2. 计数排序
3. 快速排序
4. 归并排序
5. 二叉搜索树
【解题思路】
解法1:O(nlogn)排序+遍历计数
首先手写快速排序或归并排序代码,而后遍历整个数组,统计每种数字出现的个数并输出。
遍历时,设当前在看的数值为num,数值为num的元素个数为ct。当遍历到一个与num不同的元素时,输出已经统计好的num与其个数ct。而后将num设为新的元素,ct设为1。
在遍历完整个数组后,再将最后一段的数值num和数值个数ct输出。
该解法时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
解法2:二分查找+插入排序
设一个数组a,每个元素是一个结构体对象,保存数值num与其个数ct。a数组中的元素关于属性num是升序的,可以进行二分查找。
对于每个输入的数值v,在a中二分查找num为v的元素,
- 如果存在num为v的元素,该数值num的个数应该增加1,即该元素的ct属性增加1。
- 如果不存在num为v的元素,则在a的末尾添加num为v、个数ct为1的元素,而后将新加入的元素插入到前面的有序序列中,该过程是一趟插入排序。
最后遍历a数组,输出每个元素的数值num和个数ct。
当然二分查找可以选择“找大于等于v的最小值”,或“找小于等于v的最大值”的写法。
该解法时间复杂度为
O
(
n
l
o
g
m
+
m
2
)
O(nlogm+m^2)
O(nlogm+m2),其中m为数字种类数,m最大为10000。
解法3:二叉搜索树
二叉搜索树每个结点保存数值num和其个数ct。该二叉搜索树中左孩子的num小于根的num小于右孩子的num。
每输入一个数值v,对二叉查找树进行插入操作
- 如果不存在num为v的结点,则新增结点,其num为v,ct为1。
- 如果存在num为v的结点,则该结点的ct增加1。
中序遍历二叉搜索树,输出每个结点的num和ct。以该顺序输出,自然是以num升序顺序输出。
m为数字种类数,m最大为10000,二叉搜索树中最多有m个结点。
由于一般二叉搜索树无法保证高度为
l
o
g
m
logm
logm,该解法每次插入复杂度为
O
(
m
)
O(m)
O(m),总体最坏时间复杂度为
O
(
n
∗
m
)
O(n*m)
O(n∗m)。
如果能写出红黑树或AVL或Treap,可以将时间复杂度降到
O
(
n
l
o
g
m
)
O(nlogm)
O(nlogm)
【题解代码】
解法1:O(nlogn)排序+遍历计数
- 写法1:手写快速排序
#include<iostream>
using namespace std;
#define N 200005
int n, a[N], ct, num;
void quickSort(int l, int r)
{
if(l >= r)
return;
int pivot = a[rand()%(r-l+1)+l], i = l, j = r;
while(i <= j)
{
while(a[i] < pivot)
i++;
while(a[j] > pivot)
j--;
if(i <= j)
{
swap(a[i], a[j]);
i++, j--;
}
}
quickSort(l, j);
quickSort(i, r);
}
int main()
{
srand(time(NULL));
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
quickSort(1, n);
num = a[1];//当前在看的数字
for(int i = 1; i <= n; ++i)
{
if(a[i] == num)
ct++;
else
{
cout << num << ' ' << ct << endl;
ct = 1;
num = a[i];
}
}
cout << num << ' ' << ct << endl;
return 0;
}
- 写法2:手写归并排序
#include<iostream>
using namespace std;
#define N 200005
int n, a[N], ct, num, t[N];
void mergeSort(int l, int r)
{
if(l >= r)
return;
int mid = (l+r)/2;
mergeSort(l, mid);
mergeSort(mid+1, r);
int i = l, j = mid+1, k = l;
while(i <= mid && j <= r)
{
if(a[i] <= a[j])
t[k++] = a[i++];
else
t[k++] = a[j++];
}
while(i <= mid)
t[k++] = a[i++];
while(j <= r)
t[k++] = a[j++];
for(int i = l; i <= r; ++i)
a[i] = t[i];
}
int main()
{
srand(time(NULL));
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
mergeSort(1, n);
num = a[1];//当前在看的数字
for(int i = 1; i <= n; ++i)
{
if(a[i] == num)
ct++;
else
{
cout << num << ' ' << ct << endl;
ct = 1;
num = a[i];
}
}
cout << num << ' ' << ct << endl;
return 0;
}
解法2:二分查找+插入排序
- 写法1:二分查找数值是否存在
#include <iostream>
using namespace std;
#define N 10005
struct Num
{
int num, ct;//num:数字 ct:个数
};
Num a[N];
int an;//ai:a中元素的个数
int main()
{
int n, v;
scanf("%d", &n);
scanf("%d", &v);//先输入第1个值
a[++an] = Num{v, 1};//num为v, ct为1
for(int i = 2; i <= n; ++i)
{
scanf("%d", &v);
int l = 1, r = an, m;//二分查找a[1]~a[an]中是否有v
while(l <= r)
{
m = (l+r)/2;
if(v < a[m].num)
r = m-1;
else if(v > a[m].num)
l = m+1;
else
break;
}
if(a[m].num == v)//找到数值v
a[m].ct++;//v的个数增加1
else
{//把x插入a
a[++an] = Num{v, 1};
for(int j = an; j > 1; --j)//将a[an]插入到a[1]~a[an-1]这一有序序列中
{
if(a[j].num < a[j-1].num)
swap(a[j], a[j-1]);
else
break;
}
}
}
for(int i = 1; i <= an; ++i)
printf("%d %d\n", a[i].num, a[i].ct);
return 0;
}
- 写法2:二分查找满足条件的最小值
#include <iostream>
using namespace std;
#define N 10005
struct Num
{
int num, ct;//num:数字 ct:个数
};
Num a[N];
int an;//ai:a中元素的个数
int main()
{
int n, v;
scanf("%d", &n);
scanf("%d", &v);//先输入第1个值
a[++an] = Num{v, 1};//num为v, ct为1
for(int i = 2; i <= n; ++i)
{
scanf("%d", &v);
int l = 1, r = an, m;//二分查找大于等于v的最小值
while(l <= r)
{
m = (l+r)/2;
if(a[m].num >= v)
r = m-1;
else
l = m+1;
}
if(a[l].num == v)//大于等于v的最小值就是数值v,即数值v存在
a[l].ct++;//v的个数增加1
else
{//把x插入a
a[++an] = Num{v, 1};
for(int j = an; j > 1; --j)//将a[an]插入到a[1]~a[an-1]这一有序序列中
{
if(a[j].num < a[j-1].num)
swap(a[j], a[j-1]);
else
break;
}
}
}
for(int i = 1; i <= an; ++i)
printf("%d %d\n", a[i].num, a[i].ct);
return 0;
}
解法3:二叉搜索树
#include <iostream>
using namespace std;
#define N 10005
struct Node
{
int num, ct;//num:数字 ct:个数
int left, right;
} node[N];
int root, p;
void insert(int &r, int val)//二叉搜索树插入
{
if(r == 0)
{
int np = ++p;
node[np].num = val;
node[np].ct = 1;
r = np;
return;
}
if(val < node[r].num)
insert(node[r].left, val);
else if(val > node[r].num)
insert(node[r].right, val);
else
node[r].ct++;
}
void show(int r)//中序遍历输出
{
if(r == 0)
return;
show(node[r].left);
cout << node[r].num << ' ' << node[r].ct << '\n';
show(node[r].right);
}
int main()
{
int n, v;
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> v;
insert(root, v);
}
show(root);
return 0;
}