线段树优化建图
要求支持以下四种操作:
\(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;
}
浙公网安备 33010602011771号