【NOIP2015普及组T4】推销员-优先队列

测试地址:推销员

做法:先来分析一下题目。从题目中的样例,我们可以得到一个猜想:后面的决策一定包含前面的决策。这个结论是可以证明的,证明过程这里就不赘述了。因此,我们只需要分阶段一步步在决策中添加住户即可。对于某一个决策,我们设离入口最远的住户编号是x,编号为i的住户离入口的距离是s[i],添加的疲劳值是a[i],则要添加住户无非就是两种情况:一是在最远住户之前找一个住户添加入决策中,这样新累积的疲劳值是a[i];二是在最远住户之后找一个住户添加入决策中,这样新累积的疲劳值是a[i]+s[i]*2-s[x]*2。对于这两种情况分别找出添加的疲劳值的最大值,然后再进行选择即可。可是,用直接的方法找最大值是O(n)的,这样使得整个程序是O(n^2),又因为n可达100000,因此这个方案不可行。此时我们就可以采用优先队列来处理,将找最大值的复杂度减小到O(logn)。用两个优先队列Q1,Q2分别表示最远住户前面的住户所添加的疲劳值组成的队列和最远用户后面的住户所添加的疲劳值组成的队列,其中要注意的是,Q1中第i住户所对应的元素是a[i],而在Q2中第i住户所对应的元素是a[i]+s[i]*2。然后,对于每次决策,分别取Q1和Q2的顶端元素,比较Q1的顶元素和Q2的顶元素-s[x]*2(相当于比较a[i]和a[j]+s[j]*2-s[x]*2,即两个住户所新添加的疲劳值),如果选择最远住户前面的住户,则将答案累加后直接pop即可,如果选择最远住户后面的住户,就要注意将x后移,并将新的已成为最远住户前面住户的元素加入Q1中。在操作过程中,我们用一个v数组标记该住户是否被选过,以便在提取最大值的时候不出现重复。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int n,s[100010],a[100010];
bool v[100010]={0};
typedef pair<int,int> pa;
priority_queue<pa> Q1,Q2;

int main()
{
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&s[i]);
  for(int i=1;i<=n;i++) scanf("%d",&a[i]);
  
  for(int i=1;i<=n;i++)
    Q2.push(make_pair(a[i]+s[i]*2,i));
  
  int x=0,ans=0;
  
  for(int i=1;i<=n;i++)
  {
    while(!Q1.empty()&&v[Q1.top().second]) Q1.pop();
	while(!Q2.empty()&&(v[Q2.top().second]||Q2.top().second<x)) Q2.pop();
	int t1=-1,t2=-1;
	if (!Q1.empty()) t1=Q1.top().first;
	if (!Q2.empty()) t2=Q2.top().first-2*s[x];
	if (t1>t2)
	{
	  v[Q1.top().second]=1;
	  ans+=t1;
	  Q1.pop();
	}
	else
	{
	  v[Q2.top().second]=1;
	  ans+=t2;
	  while(x<Q2.top().second)
	  {
	    Q1.push(make_pair(a[x],x));
		x++;
	  }
	  Q2.pop();
	}
	printf("%d
",ans);
  }
  
  return 0;
}


原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793888.html