1. 程式人生 > >【NOIP2015普及組T4】推銷員-優先佇列

【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\n",ans);
  }
  
  return 0;
}