洛谷 P3128 LCA+树上差分

题目链接:点我

题目描述:

FJ给他的牛棚的N(2≤N≤50,000)个隔间之间安装了N-1根管道,隔间编号从1到N。所有隔间都被管道连通了。

FJ有K(1≤K≤100,000)条运输牛奶的路线,第i条路线从隔间si运输到隔间ti。一条运输路线会给它的两个端点处的隔间以及中间途径的所有隔间带来一个单位的运输压力,你需要计算压力最大的隔间的压力是多少。

题意

在一个无根树中,不断让s到t路径中的点(包括端点)加一个单位压力,经过k次加压后,压力最大的点,压力为多少。

题解

LCA+树上差分
不妨确定根节点为1,用dis数组记录每一个节点的压力,如果要在u->v路径加压,那么可以分解为 u->lca ,lca->v 两条路分别加压。
因为差分可以做到对区间操作:
假如有一组数: 1 2 3 4 5
差分后的数组dd:1 1 1 1 1
对2到4加1 对3到5减一
只需要对差分数组直接操作,然后求一遍前缀和即可得到答案:
dd[2]++,dd[4+1]–;dd[3]–,dd[5+1]++(超过范围相当于没效果);
操作后的差分数组dd:1 2 0 1 0
前缀和:1 3 3 4 4 为操作后的最终答案。
同样利用这个思想,把差分放在树上,就变成了树上差分,这里需要对u->v中每一点+1,相当于对 u->根节点 ,v->根节点每一点+1 ,以及u,v最近公共祖先到根节点每一点减一。
操作之后u,v最近公共祖先的压力变成0,所以需要单独记录一下最近公共祖先的压力。
dis[u]++,dis[v]++,dis[lca]-=2;
用dis1单独记录最近公共祖先的压力:dis1[lca]++;
最后一遍树上前缀和,dfs搜索一遍,顺便把最大值也记录一下。
max_ans=max(max_ans,dis[cur]+dis1[cur]);

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false)
#define ll long long
#define maxn 50005
#define maxq 100005

struct node
{
    int value, order;
};
struct edge
{
    int to, next;
};

edge ff[2 * maxn];
int head[2 * maxn], num_edge;
void add_edge(int from, int to)
{
    ff[++num_edge].next = head[from];
    ff[num_edge].to = to;
    head[from] = num_edge;
}

vector<node>qq[maxq];       //离线存储输入
bool vis[maxn];
int yy[maxn], ans[maxq];     //yy为并查集合并数组

int find(int x)
{
    return yy[x] == x ? x : yy[x] = find(yy[x]);
}


void dfs(int from, int to)
{
    vis[to] = true;
    for (int i = head[to]; i; i = ff[i].next)
        if (ff[i].to != from)
            dfs(to, ff[i].to);
    for (int i = 0; i<qq[to].size(); i++)
        if (vis[qq[to][i].value] && ans[qq[to][i].order] == 0)
            ans[qq[to][i].order] = find(qq[to][i].value);
    yy[to] = find(from);
}

vector<pair<int,int> >V;

int dis[maxn];//树上差分,记录前向点(边)的差分流量
int dis1[maxn];
int max_ans=0;
void dfs2(int fa,int cur)
{
    int sum=0;
    for(int i=head[cur]; i; i=ff[i].next)
    {
        int to=ff[i].to;
        if(fa!=to)
        {
            dfs2(cur,to);
            dis[cur]+=dis[to];
        }
    }
    max_ans=max(max_ans,dis[cur]+dis1[cur]);
}

int main()
{
    IOS;
    int n, m;
    cin>>n>>m;

    for (int i = 0; i<n - 1; i++)
    {
        int x, y;
        cin>>x>>y;
        add_edge(x, y);
        add_edge(y, x);
    }

    for (int i = 0; i<m; i++)
    {
        int x, y;
        cin>>x>>y;

        V.push_back(make_pair(x,y));
        qq[x].push_back((node)
        {
            y, i + 1
        });
        qq[y].push_back((node)
        {
            x, i + 1
        });
    }

    for (int i = 0; i <= n; i++)yy[i] = i;

    dfs(1, 1);

    for(int i=0; i<V.size(); i++)
    {
        int id=i+1;
        int x=V[i].first,y=V[i].second;
        int lca=ans[id];
        dis[x]++,dis[y]++,dis[lca]-=2;
        dis1[lca]++;
        //cout<<dis[x]<<","<<dis[y]<<","<<dis[lca]<<"\n";
    }

    /*for(int i=1; i<=n; i++)
        cout<<dis[i]<<" ";
    cout<<"\n";*/
    dfs2(1,1);

    cout<<max_ans;
    return 0;
}

题目变形:点差分 -> 边差分

假如每次不是对每个点进行加压,而是对从s->t中所有的边进行加压,又该如何求呢?其实这样改,做起来还简单些,将dis的含义改变:dis[i] 代表点 i 前向边的流量。因为这样不存在最近公共祖先压力值相抵消为0的情况,所以不需要单独记录最近公共祖先的压力。差分、前缀和即可得到答案。

发布了41 篇原创文章 · 获赞 2 · 访问量 1217

猜你喜欢

转载自blog.csdn.net/qq_41418281/article/details/104064693