题意:
有个人,第个人办理事务需要时间,刚开始(时间点为)有个空闲窗口,现在按照第个人的顺序办理事务,当某个时刻发现有空闲窗口后,第个人会到那个空闲窗口办理事务
现在设第个人办理事务的截止时间为,求数组的逆序对个数
解法一(优先队列+暴力枚举求逆序对,不可AC)
我们设第个窗口已经花费的时间为,显然对于第个人,会到最小的那个窗口进行办理事务
由此我们可以维护一个优先队列(小根堆)
1. 初始化放个0到优先队列中
2. 对于第个弹出堆顶元素,设为,则
3. 将放入队列,代表对应的窗口花费时间增加
然后我们可以用双重循环来枚举所有数对,并且求出逆序对个数
代码:
class Solution { public: long long t[100001]; long long ans; long long getNumValidPairs(int n, int m, vector<int>& a) { memset(t,0,sizeof t);//多测清空 ans=0;//多测清空 priority_queue<long long,vector<long long>,greater<long long> > pq; for(int i=1;i<=m;i++){ pq.push(0); } for(int i=1;i<=n;i++){ long long x=pq.top(); pq.pop(); t[i]=x+a[i-1];//求出第i个人的截至时间 pq.push(t[i]); } for(int i=1;i<n;i++){ for(int j=i+1;j<=n;j++){ if(t[i]>t[j])ans++;//计算逆序对个数 } } return ans; } };时间复杂度:,维护优先队列的复杂度是的,双重循环计算逆序对的复杂度是,故总的时间复杂度为
空间复杂度:,数组的空间都是级别的,优先队列的空间是级别的,故总的空间复杂度为
解法二(优先队列+归并排序求解逆序对)
对于解法一中的逆序对问题,我们可以采用归并排序法来做
假设我们现在需要求解范围内逆序对的个数
我们可以把分割为和两部分
根据归并排序,我们知道数组和都是各自有序的(这边是从小到大排序) 我们记,
当我们在归并的过程中发现时,例如下图所示:
因为数组都是按照升序排列的,若,则后面所有的数字都一定大于,由此我们就可以计算贡献了
代码:
class Solution { public: long long t[100001];//第i个人完成的截止时间 long long ans;//答案 long long tmp[100001];//归并排序临时数组 void merge_sort(int l,int r){ if(l==r)return;//递归边界 int mid=(l+r)>>1; merge_sort(l,mid);//左半边 merge_sort(mid+1,r);//右半边 int k=0;//记录当前归并了多少个元素 int i=l,j=mid+1;//i是左半边指针,j是右半边指针 while(i<=mid&&j<=r){ if(t[i]<=t[j]){ tmp[++k]=t[i++]; }else{ ans+=mid-i+1;//计算贡献 tmp[++k]=t[j++]; } } while(i<=mid){ tmp[++k]=t[i++]; } while(j<=r){ tmp[++k]=t[j++]; } for(int i=l;i<=r;i++){ t[i]=tmp[i-l+1];//放回原数组 } } long long getNumValidPairs(int n, int m, vector<int>& a) { memset(t,0,sizeof t); ans=0; priority_queue<long long,vector<long long>,greater<long long> > pq; for(int i=1;i<=m;i++){ pq.push(0); } for(int i=1;i<=n;i++){ long long x=pq.top(); pq.pop(); t[i]=x+a[i-1]; pq.push(t[i]); } merge_sort(1,n);//归并排序 return ans; } };时间复杂度:,归并排序的时间复杂度是,维护优先队列的时间复杂度是,故总的时间复杂度为
空间复杂度:,数组的空间都是级别的,优先队列的空间是级别的,故总的空间复杂度为