题目
银行有 个服务窗口,假设当前有 个人等待办理业务,那么这 个人会被顺序分配一个从 1 到 的号码。
等待办理业务的流程如下:
从第 1 号到第 号顺序的进行排队。
假设当前第 1 号到第 号都正在办理或已经办理完业务,且某个窗口 A 没有客人正在办理业务,那么第 号会马上到窗口 A 办理业务。
如果有多个这样的窗口,第 号会随意选择一个窗口。
在 0 时刻,有 个窗口都没有客人正在办理业务,而 个人正在等待办理业务。
假设第 号不管在哪个窗口办理业务,办理业务的时间都为 。
求:有多少对 ,满足 ,且第 号办理业务完成的时间严格大于第 号办理业务完成的时间。
解题思路
遍历 a
,同时维护一个最小优先队列 pq
,使用数组 t
记录每个人办理业务完成的时间。
队列 pq
表示当第 i
个人开始办理业务时,m
个窗口没有客人的时间。获取最早空闲的窗口,其时间为 pq.top()
,那么第 i
个人到该窗口办理业务,其完成时间为 pq.top() + a[i]
,记录到 t[i]
中,并更新队列。
现在,求数组 t
中有多少个逆序对,此即为题目所求。
使用归并排序求逆序对
归并排序包含这样三个步骤:
- 分解:待排序的区间为 ,令 ,我们把 分成 和
- 解决:使用归并排序递归地排序两个子序列
- 合并:把两个已经排好序的子序列 和 合并起来
在待排序序列长度为 1 的时候,递归开始回升,因为默认长度为 1 的序列是排好序的。
假设有两个已排序的序列 和 等待合并。
将 和 分别指向上述两个序列。当 比 小,但比 中的其他数大时, 贡献了 个逆序对。
例如,假设是 和 。
一开始用指针 指向 的头部, 指向 的头部。此时,,6 对逆序对总数的贡献为 0。
接着,,此时 12 小于 20,12 的贡献为 1。
接着,,此时 16 小于 20,16 的贡献为 1。
接着,,此时 24 小于 54,24 的贡献为 3。
总贡献为 0 + 1 + 1 + 3 = 5,即 5 对逆序对。
C++代码
class Solution { long long cnt; void merge(vector<long long>& t, int b, int mid, int e){ if(b >= e) return; vector<long long> L(t.begin()+b, t.begin()+mid+1); vector<long long> R(t.begin()+mid+1, t.begin()+e+1); int i = 0; int j = 0; int k = b; while(i<L.size() && j<R.size()){ if(L[i]<=R[j]){ t[k] = L[i]; cnt += j; ++i; } else{ t[k] = R[j];; ++j; } ++k; } while(i<L.size()){ cnt += j; t[k] = L[i]; ++k; ++i; } while(j<R.size()){ t[k] = R[j]; ++k; ++j; } } void mergeSort(vector<long long>& t, int b, int e){ if(b >= e) return; int mid = b + (e-b)/2; mergeSort(t, b, mid); mergeSort(t, mid+1, e); merge(t, b, mid, e); } public: /** * 求解合法的(i,j)对的数量 * @param n int整型 n个人 * @param m int整型 m个窗口 * @param a int整型vector 长度为n的vector,顺序表示1-n号客人的办理业务所需时间 * @return long长整型 */ long long getNumValidPairs(int n, int m, vector<int>& a) { // write code here vector<long long> tmp(m, 0); priority_queue<long long, vector<long long>, greater<long long> > pq(tmp.begin(), tmp.end()); vector<long long> t(n); for(int i=0; i<n; ++i){ long long tmp = pq.top() + a[i]; t[i] = tmp; pq.pop(); pq.push(tmp); } cnt = 0; mergeSort(t,0,n-1); return cnt; } };