POJ3728 THE MERCHANT LCA RMQ DP

题意简述:给定一个N个节点的树,1<=N<=50000 每个节点都有一个权值,代表商品在这个节点的价格。商人从某个节点a移动到节点b,且只能购买并出售一次商品,问最多可以产生多大的利润。

算法分析:显然任意两个城市之间的路径是唯一的,商人有方向地从起点移动到终点。询问这条路径上任意两点权值之差最大为多少,且要保证权值较大的节点在路径上位于权值较小的节点之后。 

  暴力的方法是显而易见的,只要找到两个点的深度最深的公共祖先,就等于找到了这条路径,之后沿着路径走一遍即可找到最大的利润,然而无法满足50000的数据规模。

  首先考虑高效寻找LCA(公共祖先)的方法。记录ance[i][j]为节点i向上走2^j步到达的某个祖先。可以简单地列出方程 ance[i][j]=ance[ance[i][j-1]][j-1];于是找到了高效构建的方法。

  每次寻找LCA 首先将两个节点通过swim(a,b)函数转移到同一深度,然后每次找一个最小的j使得ance[a][j]==ance[b][j] 之后将节点a赋值为ance[a][j-1] 直到j=0就找到了两者的LCA

  现在我们已经找到了高效寻找LCA的方法,假设我们知道节点a到LCA的最小值minp[],LCA到节点b的最大值maxp[],

  以及买卖地点全在LCA之前可以获得的最大利润maxi[] 以及买卖地点全在LCA之后可以获得的最大利润maxI[] 显然就得到了最后的答案。 维护这些数据的方式类似于维护ance数组的方式,DP方程也很好列出, 这里就不给出了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
using namespace std;
const int maxn=50000+10;
vector<int>po[maxn];
int dep[maxn],ance[maxn][17],maxp[maxn][17],minp[maxn][17],maxi[maxn][17],maxI[maxn][17],price[maxn],n,fa[maxn];
bool vis[maxn];
int max(int a,int b)
{
 if(a>b)return a;
 	else return b;
}
int min(int a,int b)
{
 if(a>b)return b;
 	else return a;
}
queue<int> q;
void BFS_build()
{
 memset(vis,0,sizeof(vis));
 
 q.push(1);
 fa[1]=1;
 dep[1]=1;
 vis[1]=true;
 while(!q.empty())
 	{
 	 int np=q.front();q.pop();
 	 ance[np][0]=fa[np];
 	 maxp[np][0]=max(price[np],price[fa[np]]);
 	 minp[np][0]=min(price[np],price[fa[np]]);
 	 if(price[np]<price[fa[np]])maxi[np][0]=price[fa[np]]-price[np];
 	 	else maxi[np][0]=0;
 	 if(price[np]>price[fa[np]])maxI[np][0]=price[np]-price[fa[np]];
 	 	else maxI[np][0]=0;
 	 for(int i=1;i<=16;i++)//倍增DP方程 
 	    {   
 	  		ance[np][i]=ance[ance[np][i-1]][i-1];
 	  		maxp[np][i]=max(maxp[np][i-1],maxp[ance[np][i-1]][i-1]);
 	  		minp[np][i]=min(minp[np][i-1],minp[ance[np][i-1]][i-1]);
 	  		int a=maxi[np][i-1],b=maxi[ance[np][i-1]][i-1],c=0;
 	  		c=maxp[ance[np][i-1]][i-1]-minp[np][i-1];
 	  		maxi[np][i]=max(max(a,b),c);
 	  	    a=maxI[np][i-1];b=maxI[ance[np][i-1]][i-1];c;
 	  		c=maxp[np][i-1]-minp[ance[np][i-1]][i-1];
 	  		maxI[np][i]=max(max(a,b),c);
 	  		if(ance[np][i]==1)break;
 		}
 	 for(int i=0;i<po[np].size();i++)
 	 	{
 	 	 int nv=po[np][i];
 	 	 if(vis[nv])continue;
 	 	 fa[nv]=np;
 	 	 dep[nv]=dep[np]+1;
 	 	 q.push(nv);
 	 	 vis[nv]=true;
		}
	}
}
int ia,ib,mi,ma;
int ancest;
void swim(int &a,int &b)
{
 if(dep[a]==dep[b])return ;
 while(dep[a]>dep[b])
 	{
 	 int i;
 	 for(i=0;i<=16;i++)
 	 	{
 	 	 if(pow(2,i)+dep[b]>dep[a])break;
		}
	 ia=max(max(ia,maxi[a][i-1]),maxp[a][i-1]-mi);
	 mi=min(mi,minp[a][i-1]);
	 a=ance[a][i-1];
	}
 while(dep[a]<dep[b])
 	{
 	 int i;
 	 for(i=0;i<=16;i++)
 	 	{
 	 	 if(pow(2,i)+dep[a]>dep[b])break;
		}
	 ib=max(max(ib,maxI[b][i-1]),ma-minp[b][i-1]);
	 ma=max(ma,maxp[b][i-1]);
     b=ance[b][i-1];
	}
}
int solve(int a,int b)
{
 ia=0;ib=0;mi=price[a];ma=price[b];
 swim(a,b);
 if(a==b)return max(max(ia,ib),ma-mi);
  
 while(true)
 	{
 	 int i;
 	 for(i=0;i<=16;i++)
 	 	{
 	 	 if(ance[a][i]==ance[b][i])break;
		}
	 if(i==0)
	 	{
	 	 ancest=ance[a][0];
	 	 ia=max(ia,price[ancest]-mi);
	 	 ib=max(ib,ma-price[ancest]);
	 	 mi=min(mi,price[ancest]);
	 	 ma=max(ma,price[ancest]);
	 	 return max(max(ia,ib),ma-mi);
		}
			else
				{
				 ia=max(max(ia,maxi[a][i-1]),maxp[a][i-1]-mi);
				 ib=max(max(ib,maxI[b][i-1]),ma-minp[b][i-1]);
				 mi=min(mi,minp[a][i-1]);
				 ma=max(ma,maxp[b][i-1]);
				 a=ance[a][i-1];b=ance[b][i-1];
				}
	}

}
int main()
{freopen("t.txt","r",stdin);
 scanf("%d",&n);
 
 for(int i=1;i<=n;i++)
 	scanf("%d",&price[i]);
 for(int i=1;i<n;i++)
 	{
 	 int a,b;
 	 scanf("%d%d",&a,&b);
 	 po[a].push_back(b);po[b].push_back(a);
	}
 BFS_build();
 int p;
 scanf("%d",&p);
 for(int i=1;i<=p;i++)
 	{
 	 int a,b;
	 scanf("%d%d",&a,&b);
	 printf("%d
",solve(a,b));	
	}
 return 0;
}

  这个题目似乎是北大月赛的题目,不得不佩服他们题目的质量,渣校只能仰望了。

原文地址:https://www.cnblogs.com/heisenberg-/p/5471202.html