P7984 [USACO21DEC] Tickets P
\(\color{purple}\text{P7984 [USACO21DEC] Tickets P}\)
首先这个建边方式一眼就能发现用线段树优化建边。但是要注意一定要建一个虚点,把起点连向虚点,虚点连向区间,然后把权值放在起点到虚点的边上,因为到区间任意的点都只要花一次钱,这里调了很久。
然后就是关于线段树的一个细节:我们都知道线段树有 \(2n-1\) 个节点,那么我们建完树新的点编号可以从 \(2n-1\) 开始往后吗? 不行。因为普通的建树并不是所有编号都用到了,会空下一些编号,导致最后编号大于 \(2n-1\)。(动态开点当然就没这个问题了)。这里又调了很久。
我们注意到求每个点到 \(1\) 和 \(n\) 的路径的最小值,一条边只用算一次。建反图然后跑两遍 \(DJK\)(\(SPFA\) 会超时!早就死了啦!)求出每个点到 \(1\),\(n\)的最短路,加起来肯定不行,相同的边算了两遍。一个很显然的想法是找一个中转点 \(v\) ,答案就是 \(dis[s,v]+dis[v,1]+dis[v,n]\) 。如果只有一个 \(s\) ,我们就再求出 \(dis[s,?]\) 即可。但是 \(n\) 个起点肯定受不了,需要另辟蹊径。做法是:令每个点处置 \(ans[i]=dis[i,1]+dis[i,n]\) ,然后反向图上跑一遍 \(DJK\) 。其实很好理解, 存在边 \((u,v)\) ,那么 \(ans[u]=min(ans[u],ans[v]+w)\) ,其实这样的话,有点像以 \(v\) 为中转点进行了一次更新。反正你已经懂了,至少现在。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=500100,M=3e6+110;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,k,rx[N],cnt;
int tot,head[N],to[M],last[M],w[M];
void add(int v,int u,int t){to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;return;}
void build(int u,int l,int r){
cnt=max(cnt,u);
if(l==r){rx[l]=u;return;}
add(u,u<<1,0),add(u,u<<1|1,0);
int mid=(l+r)>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
return;
}
void ADD(int u,int l,int r,int L,int R,int s){
if(L<=l && r<=R){
add(s,u,0);
return;
}
int mid=(l+r)>>1;
if(L<=mid)ADD(u<<1,l,mid,L,R,s);
if(R>mid)ADD(u<<1|1,mid+1,r,L,R,s);
return;
}
int dis1[N],disn[N],rdis[N];
struct que{
int u,dis;
bool operator<(const que A)const{
return A.dis<dis;
}
};
void spfa(int *dis,int s){
priority_queue<que>q;
if(s==0){
for(int i=1;i<=cnt;i++)rdis[i]=dis1[i]+disn[i],q.push((que){i,rdis[i]});
}
else{
for(int i=1;i<=cnt;i++)dis[i]=1e16;
q.push((que){rx[s],0});dis[rx[s]]=0;
}
while(!q.empty()){
int u=q.top().u;q.pop();
for(int i=head[u];i;i=last[i]){
int v=to[i];
if(dis[v]>dis[u]+w[i])
dis[v]=dis[u]+w[i],q.push((que){v,dis[v]});
}
}
return;
}
signed main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=read(),k=read();
build(1,1,n);
for(int i=1;i<=k;i++){
int c=read(),p=read(),a=read(),b=read();
add(rx[c],++cnt,p);
ADD(1,1,n,a,b,cnt);
}
spfa(dis1,1);spfa(disn,n);
spfa(rdis,0);
for(int i=1;i<=n;i++){
if(rdis[rx[i]]>=1e16)printf("-1\n");
else printf("%lld\n",rdis[rx[i]]);
}
return 0;
}
//1. 线段树建边有误。
//2. cnt:?
//3. djk>spfa

浙公网安备 33010602011771号