線段樹優化建圖
在有些數據范圍內是不允許我們把圖上的所有邊建出來的
然后我們對編號為下標建線段樹
線段樹上的每個節點的 \(l\) 和 \(r\) 就是把 \(l\rightarrow r\) 中的所有點縮到一個點表示了
然后這里我們完善一下:
把每個點拆一下,成一個入點,一個出點,分別用兩個線段樹維護
我們在入點從上往下建邊權為 \(0\) 的邊
含義:到了表示區間的點肯定可以到子區間
出點從下往上建 \(0\) 邊
含義類似
入點線段樹的每個點向出點線段樹的對應位置的點建 \(0\) 邊 ,從這里進必然可以出
如果出現區間向點連邊:
建立一個虛擬點,把出區間拆成 \(\log\) 份,連向虛擬點,邊權為零,把入區間也拆成 \(\log\) 份 ,邊權為\(val\)
至於區間向區間連……
這里就直接建立兩個虛擬點,每個虛擬點和區間拆成的 \(\log\) 個小區間連邊即可
最短路一樣跑
例題
CF786B
模板題,那么貼一個代碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=1e5+10;
struct node{int to,dis,next;}e[N<<5];
int n,m,cnt,head[N<<2],q,s;
inline void add(int u,int v,int w)
{
e[++cnt].dis=w; e[cnt].next=head[u]; e[cnt].to=v;
head[u]=cnt; return ;
}
int res[N<<2];
inline void spfa(int s)
{
queue<int> q; q.push(s); memset(res,0x3f,sizeof(res));res[s]=0;
while(!q.empty())
{
int fr=q.front(); q.pop();
for(int i=head[fr];i;i=e[i].next)
{
int t=e[i].to,dist=e[i].dis+res[fr];
if(dist>=res[t]) continue;
res[t]=dist; q.push(t);
}
}
return ;
}
int ls[N<<2],rs[N<<2],rt1,rt2,tot,L,R;
inline void build1(int &p,int l,int r)
{
if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
build1(ls[p],l,mid); build1(rs[p],mid+1,r); add(p,ls[p],0); add(p,rs[p],0);
return ;
}
inline void build2(int &p,int l,int r)
{
if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
build2(ls[p],l,mid); build2(rs[p],mid+1,r); add(ls[p],p,0); add(rs[p],p,0);
return ;
}
inline void update(int p,int l,int r,int u,int w,int type)
{
if(L<=l&&r<=R)
{
type==2? add(u,p,w):add(p,u,w);
return ;
}int mid=(l+r)>>1;
if(L<=mid) update(ls[p],l,mid,u,w,type);
if(R>mid) update(rs[p],mid+1,r,u,w,type);
return ;
}
int opt,u,v,w;
signed main()
{
n=read(); q=read(); s=read(); tot=n; build1(rt1,1,n); build2(rt2,1,n);
while(q--)
{
opt=read();
if(opt==1){u=read(); v=read(); w=read(); add(u,v,w);}
else
{
u=read(); L=read(); R=read(); w=read();
update(opt==2?rt1:rt2,1,n,u,w,opt);
}
}
spfa(s); for(int i=1;i<=n;++i) printf("%lld ",res[i]==0x3f3f3f3f3f3f3f3f? -1:res[i]); puts("");
return 0;
}
}
signed main(){yspm::main(); return 0;}
\(\rm{POI2015 PUS}\)
直接建圖跑拓撲最長路是不可取的
我們考慮線段樹優化建圖
把虛擬點向給的點連邊,\(w=0\)
然后把剩下的區間向點連,\(w=1\)
考慮到每個點是有 \(id\),那么我們發現在拓撲的過程中線段樹上的葉子顯然會入隊
所以正確性是有保證的
最后直接拓撲最長路即可,感覺沒啥細節
[SNOI2017]炸彈
首先可以處理出來每個點的控制范圍,然后我們連邊(點向區間)
注意這里是有向邊,所以是父親到兒子連
然后我們發現可以 \(tarjan\) 出來強連通的炸彈
正確性還是可以理解的
依題意:我們縮點跑完之后接着 \(dfs\) 一發,求出來每個炸彈爆炸后影響的最左端和最右端的點
最后求答案即可
全是板子……直接打就行了
