今天我打开考试题目一看,发现四道考试题甚至看不出来是什么算法。
于是我听大佬说,这个题啊,是线段树优化建图板子题。很毒瘤的,不搞线段树优化建图就会爆\(0\)。
我害怕极了,自从我打暴力的水平提高,模拟退火用得越发熟练,玄学预估答案越发准确,我就没爆\(0\)过了。(至少有20分吧
所以我赶紧开始现学线段树优化建图,希望能在四个小时内学会模板
原理结合着线段树优化建图模板题来说
题意:
有一个n个节点的有向有权图,现在给出三个类型的边:
- 从\(u\)到\(v\)的一条边权为\(w\)的边
- 从\(u\)到区间\([l,r]\)任意一点,都有权值为\(w\)的边
- 从区间\([l,r]\)任意一点到\(u\),都有权值为\(w\)的边。
简单来说,即单点到单点,单点到区间,区间到单点。
要求求出\(1\)到其他所有边点的最短路。
首先最短路好想,直接上\(dij\)。(我不太会\(spfa\),但我估计这题不太能用\(spfa\))
难的是建图。如果直接暴力建图,真的直接每个点都向整个区间连边的话,空间是不够的
然后就有了线段树优化建图。
出树和入树
假设节点数量为\(3\),单点 \(3\) 向\([2,3]\)连边,权值为\(17\)。
那么建图应该是这样的:
初始图:
在还没有加边的时候,是这样的:
对于左边这个父亲指向儿子的线段树,叫它出树,对于右边这个儿子指向父亲的树,叫它入树
默认的,两边的对应的叶子节点应该用权值为\(0\)的无向边连接(接下来就不画了
单点向区间
接下来,加上这条单点向区间的边,图是这样的:
因为都是叶子节点所以不明显(所以题目给的什么鬼样例啊)
假设再建一个单点1向区间[1,3]连边,权值为10.
如果要跑这样一条路:\(1——>2\)
那么就是这样跑的:
因为树内部是\(0\)所以最终的路径长还是10.
区间向单点
同理,如果是区间向单点连边。反过来搞就行了
假设\([1,3]\)向\(3\)连边。并要跑这样一条路:$ 1——>3 $,如下图
那如果是区间到区间怎么搞?
区间向区间
题意:
有\(n\)个点和许多双向边,点y用\(1~n\)编号。
用\(( a , b ),( c , d )\) 表示,对于任意两个点\(x\),\(y\),\((a≤x≤b),(c≤y≤d)\) ,之间有一条边,权值为\(1\).
从\(P\)点出发,求到任意一个国家的最短路。
解题
其实很简单,只要树的区间连树的区间就行了。(之前不是叶子节点连区间嘛)
这个题因为是双向边,所以是双向的快乐要搞两遍。
又因为是权值都是\(1\),就更好搞了(指最短路)
\(code\):
//题目:T3 Path
//重构次数:3
//不压行
using namespace std;
const int N=4e6+105;
int n,m,s,h[N<<1],cnt,tot,d[N],ls[N],rs[N],a,b,c,D,x,y;
int nex[N<<1],to[N<<1],val[N<<1];
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
void add(int x,int y,int z){
to[++cnt]=y;val[cnt]=z;
nex[cnt]=h[x];h[x]=cnt;
}
void build(int &x,int &y,int l,int r){
if(l==r){x=l;y=l;return ;}
if(!x)x=++tot;if(!y)y=++tot;
build(ls[x],ls[y],l,mid);
add(ls[x],x,0),add(y,ls[y],0);
build(rs[x],rs[y],mid+1,r);
add(rs[x],x,0),add(y,rs[y],0);
}//递归建初始树
void A(int p,int l,int r,int x,int y,int z,int f){
if(x<=l &&y>=r){
if(f)add(z,p,0);else add(p,z,0);
return ;
}
if(x<=mid)A(ls[p],l,mid,x,y,z,f);
if(mid<y)A(rs[p],mid+1,r,x,y,z,f);
}
void dij(){
memset(d,0x3f,sizeof(d));
deque<int> q;d[s]=0;q.push_back(s);
while(!q.empty()){
int u=q.front();q.pop_front();
for(int i=h[u];i;i=nex[i])
if(d[to[i]]>d[u]+val[i]){
d[to[i]]=d[u]+val[i];
if(val[i])q.push_back(to[i]);else q.push_front(to[i]);
}
}
}
int main(){
n=read();m=read();s=read();
int rt1=0,rt2=0;tot=n;
build(rt1,rt2,1,n);
while(m--){
a=read();b=read();c=read();D=read();
x=++tot,y=++tot;
add(x,y,1);A(rt1,1,n,a,b,x,0);A(rt2,1,n,c,D,y,1);
x=++tot,y=++tot;
add(x,y,1);A(rt1,1,n,c,D,x,0);A(rt2,1,n,a,b,y,1);
}
dij();
for(int i=1;i<=n;i++)printf("%d\n",d[i]);
return 0;
}
朋友们,我出息了!呜呜呜……
书写ing
座右铭:我从来没有见过这样阴郁而又光明的日子。