差分[差分数组 & 树状差分]

2021-01-25 19:13

阅读:407

标签:amp   距离   父亲节   img   父节点   糖果   --   餐厅   个性   

差分[差分数组 & 树状差分]

  1. 差分数组

  • 差分数组的定义:记录当前位置的数与上一位置的数的差值.
原数组 ai 9 4 7 5 9
差分数组 bi 9 -5 3 -2 4
差分数组的前缀和 9 4 7 5 9

显然通过求前缀和可以做到单点查询

他高效的地方在于区间修改,比如我们对区间[2,4]每个元素加上5,我们只需在差分数组:b2+=5,b5?=5,然后求前缀和即可

原数组 ai 9 4 7 5 9
差分数组 bi 9 0 3 -2 -1
差分数组的前缀和 9 9 12 10 9

[1,2]仍不变, [2,4]的前缀和都加了5, 4以后的话+5,-5相抵消, 还是不变的

因此差分数组能够高效的解决区间修改单点查询的问题。

  1. 树状差分

树的以下两个性质:

  1. 任意两个节点之间有且只有一条路径。
  2. 根节点确定时,一个节点只有一个父亲节点。

如果假设我们要考虑的是从uv的路径,uvlcaa,,我们将路径拆分成 u->aa->v

如果题目要求对树上的一段路径进行操作,并询问某个点或某条边被经过的次数,树上差分就可以派上用场了。

点差分

技术图片

设原数组为a,差分数组为d。假如给d[i]+1,其实就相当于给a[i]~a[n]每个元素+1

如果给树上的一个节点xd[x] +1, 相当于a[root]~a[x]链上的每个节点都+1

假设a=lca(x,y)。 把链x~y分成两个链,x~aa~y。即d[x]+1,d[y]+1

但这样链a~root增加了2,我们让d[a]-1,此时fa[a]~root变成了-1,需要d[fa[a]]-1完美解决。

例题:松鼠的新家(luogu p3258)

Description

  • 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有 n-1 根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。
  • 松鼠想邀请小熊前来参观,并且还指定一份参观指南,他希望小熊能够按照他的指南顺序,先去 a1a1,再去 a2a2,……,最后到 anan,去参观新家。可是这样会导致重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。
  • 小熊是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。
  • 因为松鼠参观指南上的最后一个房间 anan 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

Input

  • 第一行一个正整数 n,表示房间个数
  • 第二行 n个正整数,依次描述 a1,a2,?,ana1,a2,?,an。
  • 接下来 n-1 行,每行两个正整数 x,y,表示标号 xy的两个房间之间有树枝相连。

Output

  • 一共 n 行,第 i 行输出标号为 i 的房间至少需要放多少个糖果,才能让小熊有糖果吃。

Input

5
1 4 5 3 2
1 2
2 4
2 3
4 5

output

1
2
1
2
1    
#include 
using namespace std;
const int maxn=3e5+5;
struct edge{ int to,next; }e[2*maxn];
int n, len, a[maxn], head[maxn], dep[maxn], f[maxn][21], sum[maxn];
void Insert(int u, int v){ e[++len].to=v;e[len].next=head[u];head[u]=len; }
void dfs(int u, int fa){//dfs预处理祖先, 不用多说吧
    dep[u] = dep[fa]+1; f[u][0] = fa;
    for(int i=1; (1>=1;
    }    
    if(u==v) return u;
    for(int i=20; i>=0; i--) if(f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];            
    return f[u][0];
}
void search(int u){//求每个节点的差分和
    for(int i=head[u]; i; i=e[i].next){
        int v = e[i].to;
        if(v == f[u][0]) continue;
        search(v);
        sum[u] += sum[v];
    }
}
int main(){
    scanf("%d", &n);
    for(int i=1; i

边差分

cf[i]代表从ii的父亲这一条路径经过的次数。令a=lca(u,v) 因为关于边的差分,a表示a到其父亲的那条边,不在u~v路径中,所以cf[u]++,cf[v]++,cf[a]?=2

  • 例题:luogu P2680 运输计划
  • 题意:一棵树上有m条道路,可以使任意一条道路的权值变为0,怎样使长度最长的道路长度最小。

思路 最小的长度, 肯定在 0 - maxl(最长的那条道路) 之间, 我们可以用二分在 0-maxl枚举, 每次枚举统计长度超过mid的路径的条数cnt, 找到一条被经过了cnt次的边(这条边可以影响所有长度超过mid的路径), 把符合条件的边中最长的那一条归零, 如果cnt条路径中最长的减去这条边权

#include 
using namespace std;
const int maxn=3e5+5;
int f[maxn][22],head[maxn],edge[maxn],dep[maxn],dis[maxn];
int st[maxn],ed[maxn],c[maxn],lca[maxn],len[maxn];
int n,m,lene,maxl,ans,cnt,max_edge;
struct Edge{int to,w,next;}e[maxn>= 1;
    }
    if(u==v) return u;
    for(int i=20; i>=0; i--) if(f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
    return f[u][0];
}
int getdis(int i){ return dis[st[i]] + dis[ed[i]] - 2*dis[lca[i]]; } //求第i条路径长度
int dfs2(int u){
    int tot = c[u];//计算u到其父亲这条边访问次数
    for(int i=head[u]; i; i=e[i].next){
        int v = e[i].to;
        if(v==f[u][0])continue;
        tot += dfs2(v);
    }
    if(tot==cnt) max_edge = max(max_edge, edge[u]);
    return tot;
}
bool check(int mid){
    cnt=0, max_edge=0;//cnt记录超过mid的路径数,max_edge记录要删掉的边
    for(int i=1; i mid)//超出mid的区间进行差分,并计数
            cnt++, c[st[i]]++, c[ed[i]]++, c[lca[i]]-=2;
    if(cnt==0)return 1;//没有超过的
    dfs2(1);//求出被经过cnt次,且最长的边
    return maxl-max_edge >1;
        if(check(mid)) ans = mid, r = mid-1;
        else l = mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

差分[差分数组 & 树状差分]

标签:amp   距离   父亲节   img   父节点   糖果   --   餐厅   个性   

原文地址:https://www.cnblogs.com/hzoi-poozhai/p/12859765.html

上一篇:Unity 快速定位UI

下一篇:java中的NIO


评论


亲,登录后才可以留言!