线段树优化建图板子题。。。。。。
暴力建边 \(\mathcal O(n^2)\) 肯定会 TLE
但仔细分析可以发现,题面中有一个我们非常熟悉的字眼“区间”,这启示我们,可不可以以此作为解题的突破口呢?
答案是肯定的。想到区间我们可以联想到各种我们很熟悉的 trick,如前缀和、差分、线段树等。
但对于此题而言前缀和、差分似乎都不太行,于是我们考虑线段树,利用“每一个区间都可以表示为线段树上 \(\log n\) 个区间“来减少边的个数。
我们就拿 \(2\) 操作来举例吧。现在假设假如有一个点要与 \([1,3]\) 的点连边权为 \(w\) 的边,那么我们建出线段树:
将 \([1,3]\) 拆成 \([1,2]\) 与 \([3,3]\) 然后分别连边,边权为 \(w\)(图中橙色的边):
但是仅仅只连这两条边是远远不够的,因为你只将这个点与一个区间表示的点连了边,并没有将其连到具体的单点上。
因此我们还从每个区间向其子区间连边,由于你向下走,从一个大区间对应到一个小区间没有代价,因此这些边的边权为 \(0\)。
操作 \(3\) 也同理,只不过把之前连的所有边都反向。
以上是操作 \(2\) 与操作 \(3\) 分开来考虑的情形,那么操作 \(2\) 与操作 \(3\) 相结合该怎么办呢?
显然你不能把它们揉在一棵线段树上,因为你线段树上每条边向上向下边权都为 \(0\),故从原点到每个点的最短路也为 \(0\),这……还玩个什么啊。。。。。。
因此可以想到建两棵线段树,第一棵只连自上而下的边,第二棵只连自下而上的边:
对于 \(2\) 操作,你就从第二棵线段树的叶子节点向第一棵线段树中的对应区间连边(下图中橙色的边)。
对于 \(3\) 操作,你就从第二棵线段树中的对应区间向第一棵线段树中的叶子节点连边(下图中粉色的边)。
还有一点,就是两棵线段树的叶子节点实际上是同一个点,因此要在它们互相之间连边权为 \(0\) 的边(下图中黄色的边)
图建好了,剩下来就是板子:
const int D=5e5;
int n=read(),m=read(),st=read(),dist[1000005],leaf[100005];
vector<pii> g[1000005];
struct node{
int l,r;
} s[100005<<2];
inline void build(int k,int l,int r){
s[k].l=l;s[k].r=r;
if(l==r){
leaf[l]=k;
return;
}
int mid=(l+r)>>1;
g[k].push_back(pii(k<<1,0));
g[k].push_back(pii(k<<1|1,0));
g[(k<<1)+D].push_back(pii(k+D,0));
g[(k<<1|1)+D].push_back(pii(k+D,0));
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
inline void connect(int k,int l,int r,int v,int w,int tp){
if(l<=s[k].l&&s[k].r<=r){
if(tp) g[k+D].push_back(pii(v,w));
else g[v].push_back(pii(k,w));
return;
}
int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) connect(k<<1,l,r,v,w,tp);
else if(l>mid) connect(k<<1|1,l,r,v,w,tp);
else connect(k<<1,l,mid,v,w,tp),connect(k<<1|1,mid+1,r,v,w,tp);
}
signed main(){
build(1,1,n);
fz(i,1,m){
int opt=read();
if(opt==1){
int v=read(),u=read(),w=read();
g[leaf[v]].push_back(pii(leaf[u],w));
}
else{
int v=read(),l=read(),r=read(),w=read();
connect(1,l,r,leaf[v],w,opt%2);
}
}
fz(i,1,n) g[leaf[i]].push_back(pii(leaf[i]+D,0)),g[leaf[i]+D].push_back(pii(leaf[i],0));
priority_queue<pii,vector<pii>,greater<pii> > q;
fillbig(dist);dist[leaf[st]+D]=0;q.push(pii(0,leaf[st]+D));
while(!q.empty()){
pii p=q.top();q.pop();
int x=p.se,sum=p.fi;
// cout<<x<<endl;
if(dist[x]<sum) continue;
foreach(it,g[x]){
int y=it->fi,z=it->se;
if(dist[y]>sum+z){
dist[y]=sum+z;
q.push(pii(dist[y],y));
}
}
}
fz(i,1,n){
if(dist[leaf[i]]==0x3f3f3f3f3f3f3f3fll) printf("-1 ");
else printf("%lld ",dist[leaf[i]]);
}
return 0;
}