学习记录——树上差分(BZOJ4326,BZOJ3631)

前言

最近做的很多题都与树上差分有点儿关系,太久没用有点儿忘了,用这篇博客总结一下。

什么是差分

可以先考虑差分数组,即可以在线性时间内对一个数组的进行次区间修改(区间加减)。即额外维护b数组,初始化全为0,每一次区间修改,如把数组中l~r的数都加上a,那么就把b[l]+=a,b[r+1]-=a,然后多次修改之后,b[i]数组的前缀和就是原数组第i个节点的修改的值。
对应到树上,就是把从节点x到节点y的路径上的点或者边都增加一个值。
如果点增加一个值,那么只需要额外开一个数组num[i],表示i节点增加或者减少的权值(以1为例)。所以每次修改,需要把x和y的num[x]++,num[y]++,x与y的lca(最近公共组先),然后num[lca]–,还需要把lca的父节点num[fa[lca]]–才行。
如果增加边的一个值,这时num[i]表示连接i节点与其父节点的边在修改的权值。fa[i]不再表示i节点的父节点,而是表示连接i节点与其父节点的边的序号。这时只需要num[x]++,num[y]++,num[lca]-=2。
最后需要一遍dfs来把子节点的num累加到父节点上,最后得到的num就是改变的权值。

例题:

修改边权 BZOJ4326
链接https://www.lydsy.com/JudgeOnline/problem.php?id=4326
题意:给一个树和多对起点和终点,你可以把树上任意一条边的权值变为0,求修改之后,这些起点和终点对应的路径的最大权值最小是多少。
思路:先二分答案,判断的思路就是,目前目前路径中大于mid的路径中,记录大于mid的最大值ma,所以我们一定需要修改一条权值大于等于ma边的权值为0,并且这条边还得被这些大于mid的路径全部覆盖。所以用树上差分来判断哪些边被覆盖的次数等于大于mid的路径的数量,然后如果这些边中至少有一条边的权值大于ma,就返回true,否则返回false。
时间复杂度:O(n *log(m *t))但是交上去到吗跑了13秒!因为常数实在是非常大。
当时提交代码wa到了自闭,完全不知道哪儿错了,后来发现给t赋值的语句竟然写到了调用bfs()后面
AC代码

#include<bits/stdc++.h>
#include<algorithm>
#include<complex>
#include<iostream>
#include<iomanip>
#include<ostream>
#include<cstring>
#include<string.h>
#include<string>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<cstdlib>
#include<time.h>
#include<ctime>
#include<bitset>
// #include<ext/pb_ds/assoc_container.hpp>
// #include<ext/pb_ds/hash_policy.hpp>
#define pb push_back
#define _filein freopen("C:\\Users\\zsk18\\Desktop\\in.txt","r",stdin)
#define _fileout freopen("C:\\Users\\zsk18\\Desktop\\out.txt","w",stdout)
#define ok(i) printf("ok%d\n",i)
using namespace std;
// using namespace __gnu_pbds;
typedef double db;
typedef long long ll;
typedef pair<int,int>PII;
const double PI = acos(-1.0);
// const ll MOD=1e9+7;
const ll NEG=1e9+6;
const int MAXN=3e5+10;
const int INF=0x3f3f3f3f;
const ll ll_INF=9223372036854775807;
const double eps=1e-9;
int head[MAXN*2],nex[MAXN*2],e[MAXN*2],w[MAXN*2];
int o;
void add(int x,int y,int z)
{
    e[++o]=y;w[o]=z;
    nex[o]=head[x];head[x]=o;
}
struct tasks
{
    int x,y;
    int lca;
    int w;
}q[MAXN];
int d[MAXN];
int f[MAXN][20];
int t;
int n,m;
int dis[MAXN];
int vis[MAXN];
int pre[MAXN];
void bfs()
{
    memset(d,0,sizeof(d));
    queue<int>qq;
    qq.push(1);d[1]=1;
    while(!qq.empty())
    {
        int x=qq.front();qq.pop();
        for(int i=head[x];i;i=nex[i])
        {
            int y=e[i];
            if(d[y])continue;
            pre[y]=i;
            d[y]=d[x]+1;
            dis[y]=dis[x]+w[i];
            f[y][0]=x;
            for(int j=1;j<=t;j++)
                f[y][j]=f[f[y][j-1]][j-1];
            qq.push(y);
        }
    }
}
int lca(int x,int y)
{
    if(d[x]>d[y])swap(x,y);
    for(int i=t;i>=0;i--)
    {
        // printf("x=%d y=%d\n",x,y);
        // printf("f[%d][%d]=%d\n",y,i,f[y][i]);
        if(d[f[y][i]]>=d[x])y=f[y][i];
    }
    if(x==y)return x;
    // printf("%d %d\n",x,y);
    for(int i=t;i>=0;i--)
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
void dfs(int x,int fa)
{
    for(int i=head[x];i;i=nex[i])
    {
        int y=e[i];
        if(fa==y)continue;
        dfs(y,x);
        vis[x]+=vis[y];
    }
}
bool pd(int mid)
{
    int ma=0;
    int number=0;
    for(int i=1;i<=n;i++)
        vis[i]=0;
    for(int i=1;i<=m;i++)
    {
        if(q[i].w>mid)
        {
            number++;
            ma=max(ma,q[i].w-mid);
            vis[q[i].x]++;
            vis[q[i].y]++;
            vis[q[i].lca]-=2;
        }
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==number)
        {
            if(w[pre[i]]>=ma)return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    t=(int)(log(n)/log(2))+1;
    bfs();
    // for(int i=1;i<=n;i++)
    // {
    //     printf("d[%d]=%d\n",i,d[i]);
    // }
    int l=0,r=0;
    // printf("%d\n",lca(1,8));
    // printf("%d\n",lca(9,11));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].x,&q[i].y);
        q[i].lca=lca(q[i].x,q[i].y);
        q[i].w=dis[q[i].x]+dis[q[i].y]-2*dis[q[i].lca];
        r=max(r,q[i].w);
        // printf("x=%d y=%d lca=%d w=%d\n",q[i].x,q[i].y,q[i].lca,q[i].w);
    }

    while(l<r)
    {
        int mid=(l+r)>>1;
        if(pd(mid))r=mid;
        else l=mid+1;
    }
    printf("%d\n",l);
    return 0;
}

修改点权 BZOJ3631
链接https://www.lydsy.com/JudgeOnline/problem.php?id=3631
题意:略,
思路:就是很裸的树上差分,对点进行操作就行了,简单的一批。
AC代码

#include<bits/stdc++.h>
#include<algorithm>
#include<complex>
#include<iostream>
#include<iomanip>
#include<ostream>
#include<cstring>
#include<string.h>
#include<string>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<cstdlib>
#include<time.h>
#include<ctime>
#include<bitset>
// #include<ext/pb_ds/assoc_container.hpp>
// #include<ext/pb_ds/hash_policy.hpp>
using namespace std;
// using namespace __gnu_pbds;
#define pb push_back
#define _fileout freopen("C:\\Users\\zsk18\\Desktop\\out.txt","w",stdout)
#define _filein freopen("C:\\Users\\zsk18\\Desktop\\in.txt","r",stdin)
#define ok(i) printf("ok%d\n",i)
typedef double db;
typedef long long ll;
typedef pair<int,int>PII;
const double PI = acos(-1.0);
const ll MOD=1e9+7;
const ll NEG=1e9+6;
const int MAXN=3e5+10;
const int INF=0x3f3f3f3f;
const ll ll_INF=9223372036854775807;
const double eps=1e-9;
int head[MAXN],nex[2*MAXN],e[2*MAXN];
int o;
void add(int x,int y)
{
    e[++o]=y;
    nex[o]=head[x];head[x]=o;
}
int a[MAXN];
int n;
int f[MAXN][20];
int d[MAXN];
int t;
int num[MAXN];
int vis[MAXN];
int fa[MAXN];
void bfs()
{
    queue<int>q;
    d[1]=1;
    q.push(1);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nex[i])
        {
            int y=e[i];
            if(d[y])continue;
            fa[y]=x;
            d[y]=d[x]+1;
            f[y][0]=x;
            for(int j=1;j<=t;j++)
                f[y][j]=f[f[y][j-1]][j-1];
            q.push(y);
        }
    }
}
int lca(int x,int y)
{
    if(d[x]>d[y])swap(x,y);
    for(int i=t;i>=0;i--)
        if(d[f[y][i]]>=d[x])y=f[y][i];
    if(x==y)return x;
    for(int i=t;i>=0;i--)
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
void dfs(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=nex[i])
    {
        int y=e[i];
        if(vis[y])continue;
        dfs(y);
        num[x]+=num[y];
    }
    return;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    t=(int)(log(n)/log(2))+1;
    bfs();
    for(int i=1;i<n;i++)
    {
        num[a[i]]++;
        num[a[i+1]]++;
        int mid=lca(a[i],a[i+1]);
        // printf("%d %d %d %d\n",a[i],a[i+1],mid,fa[mid]);
        num[mid]--;
        num[fa[mid]]--;
    }
    dfs(1);
    for(int i=2;i<=n;i++)
        num[a[i]]--;
    for(int i=1;i<=n;i++)
        printf("%d\n",num[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值