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
posted @ 2023-05-30 21:57  FJOI  阅读(26)  评论(0)    收藏  举报