题解 CF786B 【Legacy】
线段树优化建图板子题。。。。。。
暴力建边
但仔细分析可以发现,题面中有一个我们非常熟悉的字眼“区间”,这启示我们,可不可以以此作为解题的突破口呢?
答案是肯定的。想到区间我们可以联想到各种我们很熟悉的 trick,如前缀和、差分、线段树等。
但对于此题而言前缀和、差分似乎都不太行,于是我们考虑线段树,利用“每一个区间都可以表示为线段树上
我们就拿
将
但是仅仅只连这两条边是远远不够的,因为你只将这个点与一个区间表示的点连了边,并没有将其连到具体的单点上。
因此我们还从每个区间向其子区间连边,由于你向下走,从一个大区间对应到一个小区间没有代价,因此这些边的边权为
操作
以上是操作
显然你不能把它们揉在一棵线段树上,因为你线段树上每条边向上向下边权都为
因此可以想到建两棵线段树,第一棵只连自上而下的边,第二棵只连自下而上的边:
对于
对于
还有一点,就是两棵线段树的叶子节点实际上是同一个点,因此要在它们互相之间连边权为
图建好了,剩下来就是板子:
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;
}