当前位置: 首页 > news >正文

洛谷P3128 [USACO15DEC] Max Flow P

洛谷P3128 [USACO15DEC] Max Flow P

洛谷题目传送门

题目描述

Farmer John 在他的谷仓中安装了 N − 1 N-1 N1 条管道,用于在 N N N 个牛棚之间运输牛奶( 2 ≤ N ≤ 50 , 000 2 \leq N \leq 50,000 2N50,000),牛棚方便地编号为 1 … N 1 \ldots N 1N。每条管道连接一对牛棚,所有牛棚通过这些管道相互连接。

FJ 正在 K K K 对牛棚之间泵送牛奶( 1 ≤ K ≤ 100 , 000 1 \leq K \leq 100,000 1K100,000)。对于第 i i i 对牛棚,你被告知两个牛棚 s i s_i si t i t_i ti,这是牛奶以单位速率泵送的路径的端点。FJ 担心某些牛棚可能会因为过多的牛奶通过它们而不堪重负,因为一个牛棚可能会作为许多泵送路径的中转站。请帮助他确定通过任何一个牛棚的最大牛奶量。如果牛奶沿着从 s i s_i si t i t_i ti 的路径泵送,那么它将被计入端点牛棚 s i s_i si t i t_i ti,以及它们之间路径上的所有牛棚。

输入格式

输入的第一行包含 N N N K K K

接下来的 N − 1 N-1 N1 行每行包含两个整数 x x x y y y x ≠ y x \ne y x=y),描述连接牛棚 x x x y y y 的管道。

接下来的 K K K 行每行包含两个整数 s s s t t t,描述牛奶泵送路径的端点牛棚。

输出格式

输出一个整数,表示通过谷仓中任何一个牛棚的最大牛奶量。

输入输出样例 #1

输入 #1

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

输出 #1

9

说明/提示

2 ≤ N ≤ 5 × 1 0 4 , 1 ≤ K ≤ 1 0 5 2 \le N \le 5 \times 10^4,1 \le K \le 10^5 2N5×104,1K105

题目分析

我们把题目简化一下:

给定一颗无向图 E E E,输入 K K K个数 x , y x,y x,y,对于每个 x , y x,y x,y,将路径 [ x , y ] [x,y] [x,y]上的所有点权加 1,试求出

点权最大的点 u u u

由此,我们便很容易看出这是一道树链剖分的题,而且是重链剖分

由此,我们便有两个思路:

F i r s t : First: First:

我们将原来的树链剖分的线段树转换为一个差分,最后只需求出前缀和即可。

该思路的比较简单,我就不写了

S e c o n d : Second: Second:

由于要将一条链上的所有点权都加 1 ,那我们很自然就想到了线段树的区间修改,由于要用到懒标

记,我们的代码会很长很长,正好可以复习一下线段树区间修改

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int h[N],to[N],ne[N],tot,a[N];
void add(int u,int v){
	tot++;to[tot]=v;ne[tot]=h[u];h[u]=tot;
}
int n,m;
struct tre{
	int data,l,r,lzy;//data为区间[l,r]的区间和,lzy为懒标记
};
class Segment_tree{//线段树模板(懒标记+区间修改+区间查询)
private:
	int w[N*4];//建树时叶子节点点权
	tre tree[N*4];
	void pushup(int u){tree[u].data=tree[u*2].data+tree[u*2+1].data;}//由下而上更新data
	void add(int u,int v){tree[u].data+=(tree[u].r-tree[u].l+1)*v;tree[u].lzy+=v;}//将 u 点对应区间中的每一个点都加上 v
	void pushdown(int u){//懒标记下传到左右子树
		if(!tree[u].lzy)return;
		add(u*2,tree[u].lzy);add(u*2+1,tree[u].lzy);//左右子树分别加上 tree[u].lzy
		tree[u].lzy=0;//易错点:懒标记下传后一定要清零
	}
	void _build(int u,int l,int r){//建树
		tree[u]={0,l,r,0};
		if(l==r){tree[u].data=w[l];return;}//注意:一定是区间对应实际值,而非节点编号
		int mid=(l+r)/2;
		_build(u*2,l,mid);_build(u*2+1,mid+1,r);//分别建立左右子树
		pushup(u);//向上整合区间和
	}
	void modify(int u,int x,int y,int v){//将区间[x,y]的每一个值都加上 v
		int l=tree[u].l,r=tree[u].r;
		if(l>y||r<x)return;//如果当前节点对应区间与目标区间无交集,退出
		if(x<=l&&r<=y){add(u,v);return;}//如果当前区间为目标区间的子集,直接加
		pushdown(u);//注意:无论何时要访问子节点就要下传懒标记
		modify(u*2,x,y,v);modify(u*2+1,x,y,v);//细分区间修改
		pushup(u);//向上更新修改后的值
	}
	int getsum(int u,int x,int y){
		int l=tree[u].l,r=tree[u].r;
		if(l>y||r<x)return 0;//无交集,直接退(返回一个不影响答案的值)
		if(x<=l&&r<=y){return tree[u].data;}//为自己,返回(全部)值
		pushdown(u);//访问子(节点),传(懒)标记
		return getsum(u*2,x,y)+getsum(u*2+1,x,y);//合答案
	}
public://对外封装
	void build(int l,int r,int a[]){//以a[]为叶子节点的值建树
		memcpy(w,a,sizeof(w));
		_build(1,l,r);
	}
	void update(int l,int r,int v){modify(1,l,r,v);}//将区间[l,r]的值加上v
	int sum(int l,int r){return getsum(1,l,r);}//求区间[l,r]的和
};
class Heavy_chain_decomposition{//重链剖分
private:
	int id[N],reid[N*4],siz[N],fa[N],dep[N],son[N],top[N],tim,w[N];
//重链剖分数组:
//id[u]:u节点在线段树中的编号
//reid[u]:线段树中u点对应的节点
//siz[u]:u节点及其子树大小
//fa[u]:u节点的父节点
//dep[u]:u节点的深度(dep越大,深度越大,id越大,节点越靠下)
//son[u]:u节点的重儿子(siz最大的儿子)
//top[u]:u节点所在链的链头(dep最小的节点)
//tim:访问时间,用来标号
//w:建树数组
	Segment_tree tr;//剖分出的链对应线段树
	void dfs1(int u,int fath,int dp){//第一次深搜求出dep,siz,fa,son
//u为当前节点,fath为父节点,dp为深度
		dep[u]=dp;siz[u]=1;fa[u]=fath;//更新dep,siz,fa(siz初始值为1:包括u节点)
		for(int i=h[u];i;i=ne[i]){//向下找子节点
			int v=to[i];
			if(v==fath)continue;//不能跑回去
			dfs1(v,u,dp+1);//先向下搜,在整合siz
			siz[u]+=siz[v];//整合siz[u]
			if(siz[v]>siz[son[u]])son[u]=v;//求出重儿子
		}
	}
	void dfs2(int u,int fath,int ori){//第二次深搜求出id,reid,top
//u为当前节点,fath为父节点,ori为当前节点所在链的链头
//Q:为什么要第二次深搜才能求出id?
//A:因为我们第一次深搜是乱访问的,没有遵循先重(儿子)后轻(儿子)的顺序
		id[u]=++tim;reid[id[u]]=u;top[u]=ori;//更新id,reid,top值
		if(son[u]){dfs2(son[u],u,ori);}//有重儿子就先访问,生成重链
//重链上的点都是重儿子,这样经过的点会更多(注意不是链上点更多)
		for(int i=h[u];i;i=ne[i]){
			int v=to[i];
			if(v==fath||v==son[u])continue;
			dfs2(v,u,v);//生成轻链
		}
	}
	void build_tree(int u){//建树:建立线段树
		dfs1(u,0,0);dfs2(u,0,u);
		tr.build(1,n,w);
	}
public://对外封装
	void change(int a[]){//将a数组中每个点对应的值转化到线段树叶子节点对应的值上
		for(int i=1;i<=n;i++)w[id[i]]=a[i];
	}
	void build(int u,int a[]){//以u点的点权为a[u]建树
		change(a);
		build_tree(u);
	}
	void update(int x,int y,int v){//将路径[x,y]上的每一个点的点权都加上v
		while(top[x]!=top[y]){//一直向上跳,直到跳到同一条链上
			if(dep[top[x]]<dep[top[y]])swap(x,y);//链头深(dep大)的先跳
			tr.update(id[top[x]],id[x],v);//跳的时候将经过的点权都加v
			x=fa[top[x]];//跳到链头的父节点(更高一条链)
		}
		if(dep[x]>dep[y])swap(x,y);//已经到同一条链上了,让x的id小(dep小),方便修改
		tr.update(id[x],id[y],v);//修改
	}
	int sum(int x,int y){
		int res=0;
		while(top[x]!=top[y]){//跳(到链头)同高
			if(dep[top[x]]<dep[top[y]])swap(x,y);//(深度)深先跳
			res+=tr.sum(id[top[x]],id[x]);//累加(点权)和
			x=fa[top[x]];//(向)上跳(到更高的)链
		}
		if(dep[x]>dep[y])swap(x,y);//(深度)小在前
		res+=tr.sum(id[x],id[y]);
		return res;
	}
}tr;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n-1;i++){
		int x,y;
		cin>>x>>y;add(x,y);add(y,x);
	}
	tr.build(1,a);//一开始为空树
	for(int i=1;i<=m;i++){
		int x,y;cin>>x>>y;
		tr.update(x,y,1);
	}
	int ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,tr.sum(i,i));
	cout<<ans<<'\n';
	return 0;
}

总结

树链剖分和线段树告诉了我们一个道理:不要有畏难情绪,理解了再难的代码也能打出来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/100601.html

相关文章:

  • RUST学习笔记1:Rust开发环境搭建(Winodws11 x64)
  • QSettings用法实战(相机配置文件的写入和读取)
  • 第三届全国技能大赛广西选拔赛 《软件测试》项目样题
  • C语言深度解析:从零到系统级开发的完整指南
  • SpringBoot3+EasyExcel通过WriteHandler动态实现表头重命名
  • DAY 31 leetcode 142--链表.环形链表
  • java容器
  • 如何在不同的分辨率均能显示出清晰的字体?
  • 创作领域“<em >足</em><em>球</em><em>彩</em><em>票</em><em>计</em><em>划
  • 设计模式分类与定义(高软55)
  • xtuner微调大模型教程
  • SkyWalking+Springboot实战
  • 自定义C语言变量转换库
  • P1090合并果子(优先队列)
  • Vue3 其它API toRow和markRow
  • nacos 3.x Java SDK 使用详解
  • 【COMSOL】参数化建模以及通过MATLAB运行
  • qml中的Connections用法
  • Android Gradle 下载插件或依赖太慢
  • ansible主机清单文件以及ansible常用模块
  • 软考中级-软件设计师信息安全模块考点解析
  • docker 镜像下载的另一种方式
  • 简易指南“<em >快</em><em>3</em><em>倍</em><em>投</em><em>规</em><em>划
  • C++第13届蓝桥杯省b组习题笔记
  • 自动化工作流工具的综合对比与推荐
  • 自定义类型:结构体(1)
  • 在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
  • 5种特效融合愚人节搞怪病毒
  • 深入解析C#中的解释器模式:原理与应用
  • C语言:多线程