基础算法:差分

引入

一个简单的问题。
题目描述
给你一个长度为 n n n 的序列 a a a,需要进行 m m m 次下面的操作。

  • 将序列 a a a 中的第 l l l 个元素到第 r r r 个元素增加 x x x

m m m 次操作后的序列 a a a
数据范围
1 ≤ l ≤ r ≤ n ≤ 1 0 6 1\le l\le r\le n\le 10^6 1lrn106

差分

差分用于解决区间多次修改单次查询,思想就是用一个数组 d i f f diff diff 记录相邻两个数的差,每次修改 d i f f diff diff 中对应区间的值,最后查询时对数组 d i f f diff diff 数组求前缀和。

如将第 l ∼ r l\sim r lr 个元素增加 x x x,把 d i f f l diff_l diffl 增加 x x x,由于只修改一个区间,所以还要将 d i f f r + 1 diff_{r+1} diffr+1 减去 x x x,注意不是将 d i f f r diff_r diffr 减去 x x x,因为 r r r 也在区间范围之内。

树上差分

一般基础的线性算法涉及到树就恶心起来了。
树上差分,就是对于树上一条链或者两条链(一个点作为 LCA 的两条链拼接起来)进行多次区间修改和少数区间查询。

和线性的差分是类似的,依旧是在点上存加减值,然后从根节点一直加到两个点,但是在两点的 LCA之间要去重,原因见树上任意两点的距离。

例题

P11967 [GESP202503 八级] 割裂
树上差分的板子。
坏点对 ( x , y ) (x,y) (x,y),对于路径 x → l c a ( x , y ) x\to lca(x,y) xlca(x,y) 和路径 y → l c a ( x , y ) y\to lca(x,y) ylca(x,y),只要其中有一个点被删除,那么坏点对就不会联通。

所以,只要在删除上面两条路径中的点不会影响到好点对的联通,那么就是一个答案。

用树上差分求出好点对联通所要经过的路径就完成了。

#include<bits/stdc++.h>
//#define int long long
//#define lc p<<1
//#define rc p<<1|1
//#define lowbit(x) x&-x
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
const int N=1e6+5;
const int M=1e3+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
void putstr(string s){
	for(int i=0;i<s.size();i++)putchar(s[i]);
}
int n,m,k;
int T;
vector<int>a[N];
int dep[N];
int f[N][30];
int vis[N];
void bfs(){
	queue<int>q;
	q.push(1);
	vis[1]=1;
	dep[1]=1;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=0;i<a[x].size();i++){
			int y=a[x][i];
			if(vis[y])continue;
			dep[y]=dep[x]+1;
			f[y][0]=x;
			for(int j=1;j<=20;j++)f[y][j]=f[f[y][j-1]][j-1];
			vis[y]=1;
			q.push(y);
		}
	}
}
int LCA(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int val[N];
void dfs(int fa,int x){
	for(int i=0;i<a[x].size();i++){
		int y=a[x][i];
		if(y==fa)continue;
		dfs(x,y);
		val[x]+=val[y];
	}
}
signed main(){
	//ios::sync_with_stdio(0);
	n=read(),m=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		a[u].push_back(v);
		a[v].push_back(u);
	}
	bfs();
	while(m--){
		int u=read(),v=read();
		int lca=LCA(u,v);
		val[u]++;
		val[v]++;
		val[lca]--;
		val[f[lca][0]]--;
	}
	dfs(0,1);
	int x=read(),y=read();
	int lca=LCA(x,y);
	int ans=!val[lca];
	while(x!=lca){
		if(!val[x])ans++;
		x=f[x][0];
	}
	while(y!=lca){
		if(!val[y])ans++;
		y=f[y][0];
	}
	print(ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值