G - The merchant

The merchant

给一个树( n < 50000 ) 每个点有个权值,代表商品价格,若干个询问( q < 50000 )
对每个询问,问的是从u点走到v点(简单路径),商人在这个路径中的某点买入商品,然后在某点再卖出商品,最大盈利是多少?
注意一条路径上只能买卖一次,先买才能卖

分析

方法是离线 LCA。

对于一个询问, 假设 u , v 的最近公共祖先是 LCA,那么商人走的路径:u->LCA->v 。
那么买卖情况有三种可能:
1.从 u 到 LCA 买卖了。
2.从 LCA 到 v 买卖了。
3.从 u 到 LCA 之间买了,从 LCA 到 v 卖了。
我们要在一遍DFS中算出这三种可能的正确答案,然后作比较,得出最优的选择。为了算出最终的答案,我们需要维护一些信息,即:
1.up[u]表示从u到目前的根(LCA)的最大盈利,对应第一种情况。
2.down[u]表示从目前的根到u的最大盈利,对应第二种情况。
3.Max[u]表示到目前的根的最大值
4.Min[u]表示到目前的根的最小值
通过Max,Min计算出第三种情况,当LCA更新时,更新 up 与 down 的值。
ans=max(up[u],down[v],Max[v]-Min[u]);

按照之前LCA的做法,将已遍历的点标记vis=true.处理当前点 i 时,算出 i 对应的 u 或 v 这条路径的答案。但我们可以发现,遍历到点 i 时,从 LCA 到点 i 的信息(买卖)是不知道的。只有重新回到LCA时,才能处理从 u 到 v 的问题。所以我们需要将这个问题存入LCA中,即:
处理当前点 i 时,i 对应的 u 或 v 如果已经访问过,那么 u 或 v 的目前根节点即为LCA,这时将这个问题存入LCA的链接表中。回溯到LCA时,这条路径上的所有的信息都已经处理了出来,就可以通过已有信息处理答案了。
所以,对于每一个节点 i ,操作分为了三步:
1.处理以 i 作为起点或终点的问题,将该问题存入LCA中。
2.便利点 i 的所有子树,并在该过程中处理以 i 作为LCA的信息。
3.对于每个以 i 作为LCA的问题,计算出答案。
LCA的做法已经明确,但还有一个问题是:当一个节点回溯时,LCA会更新,那么对应的数组如何进行更新。
由于在这个算法中,我们用并查集保存子树,那么只需要在更新这个节点的fa值时,更新我们要的信息。

int FindSet(int u)
{
    if(fa[u]==u) return u;
    int p=fa[u];//保存原来的父节点。
    fa[u]=FindSet(p);//更新父节点,此时 p 的所有信息已更新完毕
    up[u]=max(up[u],max(up[p],Max[p]-Min[u]));
    //要么保持不变,要么更新为买卖在新增路径up[p]上,要么在u->p上买,在p->fa[p]上卖。
    //down的更新类似。
    down[u]=max(down[u],max(down[p],Max[u]-Min[p]));
    Max[u]=max(Max[u],Max[p]);
    Min[u]=min(Min[u],Min[p]);
    return fa[u];
}

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 50000
//信息数组
int up[MAXN+5],down[MAXN+5];
int Min[MAXN+5],Max[MAXN+5];
int n,Q;
//并查集
int fa[MAXN+5];
int FindSet(int u)
{
    if(fa[u]==u) return u;
    int p=fa[u];
    fa[u]=FindSet(p);
    up[u]=max(up[u],max(up[p],Max[p]-Min[u]));
    down[u]=max(down[u],max(down[p],Max[u]-Min[p]));
    Max[u]=max(Max[u],Max[p]);
    Min[u]=min(Min[u],Min[p]);
    return fa[u];
}
//储存路径
struct edge
{
    int v,nxt;
}es[MAXN*2+5];
int Edj[MAXN+5],ecnt;
void AddEdge(int u,int v)
{
    ecnt++;
    es[ecnt].v=v;es[ecnt].nxt=Edj[u];
    Edj[u]=ecnt;
}
//储存问题
struct query
{
    int v,id,nxt;
    bool t;
}q[MAXN*2+5];
int Qdj[MAXN+5],qcnt;
void AddQ(int u,int v,bool t,int id)
{
    qcnt++;
    q[qcnt].v=v;q[qcnt].t=t;
    q[qcnt].id=id;
    q[qcnt].nxt=Qdj[u];Qdj[u]=qcnt;
}
//将问题存入LCA中
struct Ans
{
    int fr,to,id,nxt;
}a[MAXN+5];
int Adj[MAXN+5],acnt;
void AddA(int c,int k,int u)
{
    acnt++;
    a[acnt].fr=u,a[acnt].to=q[k].v;
    a[acnt].id=q[k].id;
    if(q[k].t) swap(a[acnt].fr,a[acnt].to);
    a[acnt].nxt=Adj[c];Adj[c]=acnt;
}
//LCA
bool vis[MAXN+5];
int ans[MAXN+5];
void LCA(int u,int pre)
{
    //储存问题到LCA中
    for(int i=Qdj[u];i;i=q[i].nxt)
        if(vis[q[i].v])
        {
            int lca=FindSet(q[i].v);
            AddA(lca,i,u);
        }
    //遍历子树
    vis[u]=1;
    fa[u]=u;
    for(int i=Edj[u];i;i=es[i].nxt)
    {
        int v=es[i].v;
        if(v!=pre)
        {
            LCA(v,u);
            fa[v]=u;
        }
    }
    //算出答案
    for(int i=Adj[u];i;i=a[i].nxt)
    {
        int d=a[i].id;
        FindSet(a[i].fr);FindSet(a[i].to);
        ans[d]=max(up[a[i].fr],down[a[i].to]);
        ans[d]=max(ans[d],Max[a[i].to]-Min[a[i].fr]);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&Max[i]);
        Min[i]=Max[i];
    }
    int u,v,fr,to;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        AddEdge(u,v);
        AddEdge(v,u);
    }
    scanf("%d",&Q);
    for(int i=0;i<Q;i++)
    {
        scanf("%d%d",&fr,&to);
        AddQ(fr,to,0,i);
        AddQ(to,fr,1,i);
    }
    LCA(1,0);
    for(int i=0;i<Q;i++)
        printf("%d\n",ans[i]);
}

猜你喜欢

转载自blog.csdn.net/qq_41343943/article/details/79422614
G
G 1