题意简述:给定一个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; }
这个题目似乎是北大月赛的题目,不得不佩服他们题目的质量,渣校只能仰望了。