图论——树上前缀和和树上差分(LCA应用)

树上前缀和

原理

在这里插入图片描述

应用

P4427 求和

P4427

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
const LL mod=998244353;
const int N=3e5+10,M=2*N; 
int n,m;
int h[N],to[M],ne[M],tot;
int fa[N][22];//fa[u][i]表示从u向上跳2^i层的祖先结点
int dep[N];   //dep[v]表示v的深度
LL mi[60];   //mi[j]表示dep[v]的j次幂
LL s[N][60]; //s[v][j]表示从根到v的路径节点的深度的j次幂之和

void add(int a, int b){ 
  to[++tot]=b,ne[tot]=h[a],h[a]=tot;
}
void dfs(int u, int f){ //预处理节点信息
  for(int i=1; i<=20; i++) 
    fa[u][i]=fa[fa[u][i-1]][i-1];
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(v==f) continue;
    fa[v][0]=u; dep[v]=dep[u]+1;
    for(int j=1;j<=50;j++) mi[j]=mi[j-1]*dep[v]%mod;
    for(int j=1;j<=50;j++) s[v][j]=(mi[j]+s[u][j])%mod;
    dfs(v, u);
  }
}
int lca(int u, int v){ //倍增法求lca
  if(dep[u]<dep[v])swap(u, v);
  for(int i=20; ~i; i--)
    if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
  if(u==v) return v;
  for(int i=20; ~i; i--)
    if(fa[u][i]!=fa[v][i]) u=fa[u][i], v=fa[v][i];
  return fa[u][0];
}
int main(){
  scanf("%d",&n); 
  for(int i=1,a,b; i<n; i++){
    scanf("%d%d",&a,&b);
    add(a,b); add(b,a);
  }
  mi[0]=1; dfs(1, 0);
  
  scanf("%d",&m);
  for(int i=1,u,v,k;i<=m;i++){
    scanf("%d%d%d",&u,&v,&k);
    int l=lca(u,v);
    LL ans=(s[u][k]+s[v][k]-s[l][k]-s[fa[l][0]][k]+2*mod)%mod;
    printf("%lld\n",ans);
  }
}

树上差分

原理

在这里插入图片描述

应用

P3128 Max Flow P

P3128

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+10;
int n,k,x,y;
vector<int> e[N];
int fa[N][20],dep[N],diff[N];
int ans=-1;
void dfs(int u,int f){
	fa[u][0]=f,dep[u]=dep[f]+1;
	for(int i=1;i<=19;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(auto v:e[u]){
		if(v!=f){
			dfs(v,u);
		}
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--){
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	}
	if(u==v) return u;
	for(int i=19;i>=0;i--){
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	}
	return fa[u][0];
}

void dfs2(int u,int f){
	for(auto v:e[u]){
		if(v!=f){
			dfs2(v,u);
			diff[u]+=diff[v];
		}
	}
	ans=max(ans,diff[u]);
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	scanf("%d%d",&n,&k);//用scanf和printf比较好,
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,0);
	while(k--){
		scanf("%d%d",&x,&y);
		int l=lca(x,y);
		diff[x]++,diff[y]++,diff[l]--,diff[fa[l][0]]--;
	}
	dfs2(1,0);
	printf("%d",ans);
	return 0;
} 

砍树(蓝桥杯真题)

传送门
在这里插入图片描述
本题思路:将树上区间加操作,最后统计每条边总和,这马上想到差分,因为是树上,所以使用树上差分

#include <iostream>
#include <algorithm>
#include <vector>
#include<map>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 1e5 + 50;

vector<int> e[maxn];
ll fa[maxn], dep[maxn], son[maxn], siz[maxn], top[maxn];
ll diff[maxn];
map<pii,ll> id;
void dfs1(ll u, ll f) {
    fa[u] = f;
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    for (auto x: e[u]) {
        ll v = x;
        if (v == f) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(ll u, ll t) {
    top[u] = t;
    if (!son[u]) return;
    dfs2(son[u], t);
    for (auto x: e[u]) {
        ll v = x;
        if (v != son[u] && v != fa[u]) dfs2(v, v);
    }
}

ll lca(ll x, ll y) {
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    return dep[x] > dep[y] ? y : x;
}

void dfs(int x, int fx) {
    for (auto y: e[x]) {
        if (y == fx) continue;
        dfs(y, x);
        diff[x] += diff[y];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
        id[{u,v}]=i;
        id[{v,u}]=i;
    }
    dfs1(1, 1);
    dfs2(1, 1);
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        diff[u]++, diff[v]++, diff[lca(u, v)] -= 2;
    }
    dfs(1, 0);
    ll ans = -1;
    for (int i = 0; i <= n; i++){
        if (diff[i] >= m){
            ans=max(ans,id[{i,fa[i]}]);
        }
    }
    cout << ans << endl;
    return 0;
}

注意:以两个点存储边号,标记。边差分原理是,将边差分转换为点差分,除根节点外,每个点都代表点上面的那条边。

U143800 暗之连锁

传送门

在这里插入图片描述

难点:

1.当边累积超过1次,则说明删除此边不能形成满足条件,所以不会影响答案。
2.当累积为1次时,答案加一,删除此边和构成环的附加边。
3.当累积为0次时,答案加m,说明删除此边就满足条件,然后随便删除一条附加边即可。

然后用set存有哪些点,最好答案还要减去m,因为根节点为0,但是其不表示边。注意set适合去重,存点,但是有顺序的时候得慎重考虑,因为set会自动排序。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,k,x,y;
vector<int> e[N];
set<int> st;
int fa[N][20],dep[N];
ll diff[N];
ll ans=0;
void dfs(int u,int f){
	fa[u][0]=f,dep[u]=dep[f]+1;
	for(int i=1;i<=19;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(auto v:e[u]){
		if(v!=f){
			dfs(v,u);
		}
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--){
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	}
	if(u==v) return u;
	for(int i=19;i>=0;i--){
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	}
	return fa[u][0];
}

void dfs2(int u,int f){
	for(auto v:e[u]){
		if(v!=f){
			dfs2(v,u);
			diff[u]+=diff[v];
		}
	}
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	scanf("%d%d",&n,&k);
	int m=k;
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
		st.insert(x);
		st.insert(y);
	}
	dfs(1,0);
	while(k--){
		scanf("%d%d",&x,&y);
		int l=lca(x,y);
		diff[x]++,diff[y]++,diff[l]-=2;
	}
	dfs2(1,0);
	for(auto x:st){
		if(diff[x]==1) ans++;
		else if(!diff[x]) ans+=m;
	}
	printf("%d",ans-m);
	return 0;
} 

P3258 松鼠的新家

传送门
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
int n,k,x,y;
vector<int> e[N];
int fa[N][22],dep[N],a,b,t[N];
ll diff[N];
ll ans=0;
void dfs(int u,int f){
	fa[u][0]=f,dep[u]=dep[f]+1;
	for(int i=1;i<=21;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(auto v:e[u]){
		if(v!=f){
			dfs(v,u);
		}
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=21;i>=0;i--){
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	}
	if(u==v) return u;
	for(int i=21;i>=0;i--){
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	}
	return fa[u][0];
}

void dfs2(int u,int f){
	for(auto v:e[u]){
		if(v!=f){
			dfs2(v,u);
			diff[u]+=diff[v];
		}
	}
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&t[i]);
	}
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,0);
	for(int i=1;i<n;i++){
		x=t[i-1],y=t[i];
		int l=lca(x,y);
		diff[x]++,diff[y]++,diff[l]--,diff[fa[l][0]]--;
	}
	dfs2(1,0);
	for(int i=1;i<n;i++) diff[t[i]]--;//要先减,看清楚题意要求的输出
	for(int i=1;i<=n;i++){
		printf("%d\n",diff[i]);
	}
	return 0;
} 

P2680 运输计划

传送门

题意:让一条边长度(时间)变为0,求所有运输计划所需最大时间最小。求最大值最小——二分答案。

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,M=2*N;

int h[N],e[M],w[M],ne[M],idx;//链式向前星 
int fa[N],son[N],sz[N],top[N],dep[N];//树剖求最近公共祖先 
int n,m,R,L,cnt;//R表示最大航道飞行时间,L表示最大每条边的飞行时间
//diff树上差分数组,sum树上前缀和数组 
int diff[N],sum[N],dnf[N],val[N];//dnf从祖宗节点重新映射每个树节点,val[i]存储点i上面的边长度。 
void add(int a,int b,int c){
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
struct node{
	int u,v,l,d;//存储运输计划的两个端点和最近公共祖先节点,所需耗时时间 
}edge[N];

void dfs1(int u,int f,int len){//预处理除了top的数组,和前缀和数组 
	fa[u]=f,dep[u]=dep[f]+1,sz[u]=1,sum[u]=len,dnf[++cnt]=u;//dnf和val这个技巧要学会 
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==f) continue;
		dfs1(j,u,len+w[i]);
		val[j]=w[i];
		sz[u]+=sz[j];
		if(sz[son[u]]<sz[j]) son[u]=j;
	}
}

void dfs2(int u,int t){//预处理top数组,重链 
	top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
void dfs3(int u){//树上差分求和 
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==fa[u]) continue;
		dfs3(v);
		diff[u]+=diff[v];
	}
}
bool check(int x){
	memset(diff,0,sizeof diff);
	int count=0;
	for(int i=1;i<=m;i++){
		if(edge[i].d>x){
			count++;
			int u=edge[i].u,v=edge[i].v,l=edge[i].l;
			diff[u]++,diff[v]++,diff[l]-=2;
		}
	}
	dfs3(1);
	for(int i=1;i<=n;i++){
		if(val[dnf[i]]>=R-x&&diff[dnf[i]]>=count)//有一条边长度大于等于缺少的部分,并且被覆盖次数大于count次 
			return true;
	}
	return false;
}
int main(){
	memset(h,-1,sizeof h);
	int a,b,c;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c),add(b,a,c);
		L=max(L,c);
	}
	dfs1(1,0,0),dfs2(1,1);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		int l=lca(a,b);
		int d=sum[a]+sum[b]-sum[l]*2;
		edge[i]={a,b,l,d};
		R=max(R,d);
	}
	int l=R-L-1,r=R+1;
	while(l+1<r){
		int mid=l+r>>1;
		if(check(mid)) r=mid;
		else l=mid;
	} 
	printf("%d\n",r);
}

开心,一遍敲过,想代码时间应该占完成题目总时间的七成,想好了,bug才很少。
经验:
1.用点表示点上面边的长度
2.二分答案技巧。

内容概要:本文档是详尽的 Android SDK 中文帮助文档,介绍了 Android SDK 的核心概念、组件、开发环境搭建、基础开发流程及常用工具使用指南。首先解释了 Android SDK 的定义及其核心价值,即提供标准化开发环境,使开发者能高效构建、测试、优化 Android 应用。接着详细列出了 SDK 的核心组件,包括 Android Studio、SDK Tools、Platform Tools、Build Tools、Android 平台版本系统镜像。随后,文档提供了详细的环境搭建步骤,适用于 Windows、macOS Linux 系统,并介绍了基础开发流程,以“Hello World”为例展示了从创建项目到运行应用的全过程。此外,还深入讲解了 ADB、AVD Manager SDK Manager 等核心工具的功能使用方法。最后,文档涵盖了调试与优化工具(如 Logcat、Profiler Layout Inspector)、关键开发技巧(如多版本 SDK 兼容、Jetpack 库的使用资源文件管理)以及常见问题的解决方案。 适合人群:具有初步编程知识,希望深入了解 Android 应用开发的开发者,尤其是新手开发者有一定经验但需要系统化学习 Android SDK 的技术人员。 使用场景及目标:①帮助开发者快速搭建 Android 开发环境;②指导开发者完成基础应用开发,理解核心工具的使用;③提高开发效率,掌握调试与优化技巧;④解决常见开发过程中遇到的问题。 阅读建议:此文档内容全面且实用,建议读者按照章节顺序逐步学习,结合实际开发项目进行练习,尤其要注意动手实践环境搭建基础开发流程,同时参考提供的扩展学习资源,进一步提升开发技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值