线段树优化建图

要求支持以下四种操作:
\(1\):\(u\rightarrow v\)连边。
\(2\):\(u\rightarrow [l,r]\)连边。
\(3\):\([l,r]\rightarrow v\)连边。
\(4\):\([l_1,r_1]\rightarrow [l_2,r_2]\)连边。
区间操作想到线段树,可以把区间拆成\(O(logn)\)个线段树上区间,分别建立外向线段树和内向线段树。
对于所有连入一个区间操作,将边连至外向树,这代表连入一个大区间的边,在经过外向树树边后,确实可以到达所有区间内叶子节点。
对于所有从一个区间连出的操作,将边从内向树连出,这代表着区间内所有叶子节点,在经过内向树树边后,确实可以到达目标节点。
未来方便,将外向树的编号从\(1\)开始,内向树的编号从\(4n\)开始,单个节点编号从\(8n\)开始,且要与两颗树的叶子相连。
区间连区间按照朴素方法,需要连\((O{log}^2n)\)条边,但实际上可以用优化建图中另外一种优化方式,中转点优化建图,来减少连边数。
这样总点数为\(O(10n)\),总边数为\(O(12n+mlogn)\)。

\(P6348\)
https://www.luogu.com.cn/problem/P6348
题解:只有区间向区间连边权为\(1\)的双向边。模拟上述流程优化建图,跑\(01bfs\)即可。

#include <bits/stdc++.h>

using namespace std;

const int N=1e7+10;

int n,m,num,S;
int d[N];
int h[N],e[N],w[N],ne[N],idx;
bool st[N];

void add(int a,int b,int c){
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

struct SegmentTree{
	int id;
	struct Node{
		int l,r;
	}tr[4*N];
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r){
			add(u+4*id*n,l+8*n,0);
			add(l+8*n,u+4*id*n,0);
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		if(!id) add(u,u<<1,0);
		else add((u<<1)+4*n,u+4*n,0);
		build(u<<1|1,mid+1,r);
		if(!id) add(u,u<<1|1,0);
		else add((u<<1|1)+4*n,u+4*n,0);
	}
	void query(int u,int l,int r,vector<int> &q){
		if(tr[u].l>=l&&tr[u].r<=r){
			q.push_back(u+4*id*n);
			return;
		}
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) query(u<<1,l,r,q);
		if(r>mid) query(u<<1|1,l,r,q);
	}
}tree1,tree2;

void connect(int l1,int r1,int l2,int r2){
	vector<int> q1,q2;
	tree2.query(1,l1,r1,q1);
	tree1.query(1,l2,r2,q2);
	++num;
	for(int i=0; i<q1.size(); i++) add(q1[i],num,1);
	for(int i=0; i<q2.size(); i++) add(num,q2[i],0);
}

void bfs(){
	memset(d,0x3f,sizeof d);
	deque<int> q;
	d[S+8*n]=0,q.push_front(S+8*n);
	while(q.size()){
		int t=q.front();
		q.pop_front();
		if(st[t]) continue;
		st[t]=1;
		for(int i=h[t]; i!=-1; i=ne[i]){
			int j=e[i];
			if(st[j]) continue;
			if(d[t]+w[i]<d[j]){
				d[j]=d[t]+w[i];
				if(!w[i]) q.push_front(j);
				else q.push_back(j);
			}
		}
	}
}

int main(){
	memset(h,-1,sizeof h);
	cin >> n >> m >> S;
	tree1.id=0;
	tree1.build(1,1,n);
	tree2.id=1;
	tree2.build(1,1,n);
	num=9*n;
	while(m--){
		int a,b,c,d;
		cin >> a >> b >> c >> d;
		connect(a,b,c,d);
		connect(c,d,a,b);
	}
	bfs();
	for(int i=1; i<=n; i++) cout << d[i+8*n] << endl;
	return 0;
}

\(P8021\)
https://www.luogu.com.cn/problem/P8021
题解:令左侧点为强盗,右侧点为时间,连边\((i,j,c_i)\),\(j\in [a_i,b_i-1]\),显然为二分图。
选取一组匹配,钦定匹配边\((i,j,c)\)意义为强盗\(i\)选择了在时间\(j\)进行盗窃,而保安恰好也选择了在时间\(j\)制止强盗\(i\)。
发现一组匹配正好对应一种局面,于是最多可能挽回的损失,即为此二分图的最大权匹配。
由于权值均为正数,所以可以用最大费用最大流解决。
但是直接连边,边数为\(O(n^2)\)的,网络流算法不支持如此规模的边数,同样可以用线段树优化建图。

#include <bits/stdc++.h>

using namespace std;

const int N=1e6+10,INF=1e9;

int n,m,num,S,T;
int d[N],pre[N],limit[N];
int h[N],e[N],f[N],w[N],ne[N],idx;
bool st[N];

struct NetworkFlow{
	void add(int a,int b,int c,int d){
		e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;
		e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
	}
	bool spfa(){
		memset(d,-0x3f,sizeof d);
		memset(limit,0,sizeof limit);
		queue<int> q;
		d[S]=0,st[S]=1,limit[S]=INF,q.push(S);
		while(q.size()){
			int t=q.front();
			st[t]=0,q.pop();
			for(int i=h[t]; i!=-1; i=ne[i]){
				int j=e[i];
				if(f[i]&&d[t]+w[i]>d[j]){
					d[j]=d[t]+w[i];
					pre[j]=i;
					limit[j]=min(limit[t],f[i]);
					if(!st[j]) st[j]=1,q.push(j);
				}
			}
		}
		return limit[T]>0;
	}
	void EK(int &flow,int &cost){
		flow=cost=0;
		while(spfa()){
			int t=limit[T];
			flow+=t,cost+=t*d[T];
			for(int i=T; i!=S; i=e[pre[i]^1])
				f[pre[i]]-=t,f[pre[i]^1]+=t;
		}
	}
}NF; 

struct SegmentTree{
	int id;
	struct Node{
		int l,r;
	}tr[4*N];
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r){
			if(!id) NF.add(u,l+8*m,INF,0);
			else NF.add(l+8*m,u+4*m,INF,0);
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		if(!id) NF.add(u,u<<1,INF,0);
		else NF.add((u<<1)+4*m,u+4*m,INF,0);
		build(u<<1|1,mid+1,r);
		if(!id) NF.add(u,u<<1|1,INF,0);
		else NF.add((u<<1|1)+4*m,u+4*m,INF,0);
	}	
	void query(int u,int l,int r,vector<int> &q){
		if(tr[u].l>=l&&tr[u].r<=r){
			q.push_back(u+4*id*m);
			return;
		}
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) query(u<<1,l,r,q);
		if(r>mid) query(u<<1|1,l,r,q);
	}
}tree1,tree2;

void connect(int l1,int r1,int l2,int r2,int f,int w){
	vector<int> q1,q2;
	tree2.query(1,l1,r1,q1);
	tree1.query(1,l2,r2,q2);
	++num;
	for(int i=0; i<q1.size(); i++) NF.add(q1[i],num,f,w);
	for(int i=0; i<q2.size(); i++) NF.add(num,q2[i],f,0);
}

int main(){
	memset(h,-1,sizeof h);
	cin >> n;
	m=n+5000;
	tree1.id=0;
	tree1.build(1,1,m);
	tree2.id=1;
	tree2.build(1,1,m);
	num=9*m;
	for(int i=1; i<=n; i++){
		int a,b,c;
		cin >> a >> b >> c;
		connect(i,i,a+n,b-1+n,1,c);
	}
	S=num+1,T=num+2;
	for(int i=1; i<=n; i++) NF.add(S,i+8*m,1,0);
	for(int i=1; i<=5000; i++) NF.add(i+n+8*m,T,1,0);
	int flow,cost;
	NF.EK(flow,cost);
	cout << cost << endl;
	return 0;
}
posted @ 2025-03-26 19:24  junliang123  阅读(331)  评论(0)    收藏  举报