5.士兵站队 题目:训练场上n(1≤n≤50000)个高矮都不相同的士兵从左到右排成一行,依次编号为1,2,…,n。 第i个士兵的身高H(i),由于采用特殊单位,H(i)满足1≤H(i)≤2000000000。 设第i个士兵右侧最近的比他个高的士兵编号为j,则第i个士兵可看到在他的右侧比他矮的士兵的个数S(i)=j-i-1。(不考虑客观因素,比如视力范围等-,-) 求S(1)+S(2)+…+S(n)。 输入: 标准输入。 第一行为整数n,表示士兵的个数。 第二行n个整数,用一个空格隔开。分别表示编号为1,2。。。n的士兵的身高 输出: S(1)+S(2)+…+S(n)的结果 例: 输入 6 10 3 7 4 12 2 输出 5 主函数: #include <iostream> #include <time.h> using namespace std; int Sum(int h[], int n); int main(void) { time_t startTime; time_t endTime; const int n = 50000; int h[n] = {0}; int temp, p; //获取互不相同的一组数列 for (int i=0; i<n; i++) h[i] = i + 1; for (int i=n-1; i>0; i--)//洗牌,使得数的大小随机排列 { p = rand()%i; temp = h[i]; h[i] = h[p]; h[p] = temp; } //计时 time(&startTime); int s; for (int i=1; i<10000; i++) s = Sum(h, i); time(&endTime); cout << "sum = " << s << endl; cout << "time = " << difftime(endTime, startTime) << endl; system("pause"); return 0; } 方法一:根据题意可以得到一个很简单的算法,那就是遍历数组,依次累计每个元素右边比它小的元素,直到遇到比它大的元素为止。算法是时间复杂度为O(n^2)。 int Sum(int h[], int n) { int sum = 0; for (int i=0; i<n; i++) { for (int j=i+1; h[j]<h[i] && j<n; j++) sum++; } return sum; } 或者 int Sum(int h[], int n) { int i, j; int sum = 0; for (i=0; i<n; i++)//从左到右遍历每一个士兵 { for (j=i+1; h[j]<h[i] && j<n; j++)//累计士兵右边比他矮的人数 ; sum += j - i - 1;//累积总人数 } return sum; } 方法二:一个分治算法,思路和方法一一样,都是遍历数组,求出每一个S(i),然后相加,区别在于不是从左到右依次计算,而是以当前的最高人为中心,分别计算其左右两边的人。算法是时间复杂度为O(n^2)。 int F(int h[], int low, int high) { int i; for (i=low+1; h[i]<h[low] && i<high; i++)//累计士兵右边比他矮的人数 ; int sum = i - low - 1; if (i > low + 1) sum += F(h, low+1, i); if (i < high-1) sum += F(h, i, high); return sum; } int Sum(int h[], int n) { F(h, 0, n); } 方法三:以空间换时间,设置一个临时数组来存储原数组中的递减序列,依次将原数组中的元素与序列中元素比较,看是否可以将其插入到序列中。插入的法则为:若h[i] > temp[0],即下一个士兵高度大于序列中任一高度,则用h[i]覆盖原序列中的所有元素,即top = 0;若h[i] < temp[top],即下一个士兵高度小于序列中任一高度,这样只要将h[i]插入到序列的尾部,即++top;若不是上述两种情况,即h[i]只比序列中的一部分元素大,这样只要用h[i]覆盖原序列中的比它小的元素,我们在序列中找到h[i]的插入位置就可以了。最后更新序列的元素,并累积序列的长度。这样时间复杂度可以降低到O(n)。 这里借鉴了http://www.programfan.com/club/的yxs0001的代码,表示感谢! int Insert(int h[], int n, int x)//二分法求插入的位置 { int low = 0, high = n; int m; while (low <= high)//确保插入的位置是在low处,循环体最终总是执行low = m + 1; { m = (low + high) / 2; if (h[m] > x) low = m + 1; else high = m - 1; } return low; } int Sum(int h[], int n) { int *temp = new int[n];//设置一个临时数组来存储原数组中的递减序列 int top = 0;//临时数组栈顶 int sum = 0; int i; temp[top] = h[0]; for (i=1; i<n; i++) { if (h[i] > temp[0])//下一个士兵高度大于序列中任一高度 top = 0; else if (h[i] < temp[top])//下一个士兵高度小于序列中任一高度 ++top; else //下一个士兵高度在序列中将其插入序列 top = Insert(temp, top, h[i]); temp[top] = h[i]; sum += top; } delete []temp; return sum; } 另外,http://www.programfan.com/club/的Kummerwu也写了一个很有趣的代码,他设置了一个结构,需要更多的空间,但是代码也更好理解,更高效。而且Kummerwu是一个很幽默的人,他幽默的注释,给程序带来了不少生趣。 typedef struct taller { int h; /* 第一个比自己高的士兵的身高 */ int pos; /* 该士兵的位置 */ }TALLER; //说白了,就是一个出栈,入栈的过程,每个元素入栈一次,在入栈的过程中, //可以将比自己小的元素踢出栈,所以最坏情况下,所有元素入栈,出栈各操作一次 //故复杂度为O(n) int Sum(int h[], int n) { const int MAX_SOLDIER_CNT = 50000; const int MAX_SOLDIER_H = 2000000000; static TALLER talls[MAX_SOLDIER_CNT + 2]; //高个子俱乐部 TALLER * cur_tall = talls; int count = 0; cur_tall->h = MAX_SOLDIER_H + 1; //标兵(靠,这家伙还真高呀) cur_tall->pos = n; for (n-=2; n>=0; n--) { //如果我比右侧的那个家伙高(俺也算个高个子了) if (h[n] > h[n+1]) { //把比自己矮的士兵踢出栈,(呵呵,爽! ) while (cur_tall->h < h[n]) { cur_tall--; //这里可能有优化的空间??可以用二分法插入 } //算算中间有几个矮冬瓜(现在cur_tall是第一个比我高的家伙) count += cur_tall->pos - n - 1; //呵呵,俺光荣的加入高个子俱乐部了 :). cur_tall++; cur_tall->h = h[n]; cur_tall->pos = n; } //靠,右侧那家伙比我高 //(算了,让他进入高个子堆吧,反正也没啥油水可以捞) else { cur_tall++; cur_tall->h = h[n+1]; cur_tall->pos = n+1; } } return count; }

评论