码迷,mamicode.com
首页 > 其他好文 > 详细

CF::Gym 102174G - 神圣的 F2 连接着我们

时间:2020-07-11 23:19:32      阅读:78      评论:0      收藏:0      [点我收藏+]

标签:lld   保存   直接   node   经典   问题   cin   mat   简单   

我知道我现在应该补eJOI题解,但是如果我真的补了,那我不就不是鸽子了么(大雾

CF::Gym题目页面传送门

题意见CF::Gym。

这显然是个最短路。

首先假设我们已经很优地把折跃棱镜的效果用边建出来了。设\(dis_{i,j}\)表示第一个县的据点\(i\)到第二个县的据点\(j\)的最短路,那么答案显然是\(\max\limits_{i=1}^p\left\{\min\limits_{j=1}^q\left\{dis_{x_i,y_j}\right\}\right\}\)。这样多源多汇的最短路肯定是吃不消的。考虑一个很简单的优化,建一个虚拟节点,从所有第二个县的建筑据点向这个虚拟节点连长度为\(0\)的有向边,这样那个式子里面那个\(\min\)就变成\(x_i\)到虚拟节点的最短路了,就变成一个多源单汇最短路。但还是不能直接求,把边都取反一下,从虚拟节点出发往第一个县的战斗据点走即可跑堆优化Dijkstra。

接下来考虑怎么“很优地把折跃棱镜的效果用边建出来”。这是一个非常经典的trick:线段树优化建图(没错这可以看作一篇学习笔记)。

考虑如何从区间\([l1,r1]\)的每个点向区间\([l2,r2]\)的每个点连有向边。只需要建两棵线段树分别表示出和入,每个节点表示它所表示的区间内点的整体(以它为边的端点表示区间内所有点都是端点)(没错,除了叶子都算虚拟节点),这是一个叠加态。我们可以在出树中从每个儿子向爸爸连长度为\(0\)的有向边、在入树种从每个爸爸向儿子连长度为\(0\)的有向边来实现叠加态具体化。然后新建虚拟节点,将\([l1,r1]\)在出树中分解成若干个节点,都连向虚拟节点,再从虚拟节点连向\([l2,r2]\)在入树中分解成的若干节点。至于边权,你只需要使所有出边边权相同、所有入边边权相同,并且出边边权加入边边权等于原问题中的边权,正确性都显然(一般会令出边边权为原问题边权,入边边权为\(0\))。这样边数复杂度显然是\(\mathrm O(\log n)\)的。

回到本题。遗憾的是,线段树优化建图并没法一次性建无向边,因为要无向的话,首先两棵线段树内爸爸儿子连接的边都得无向,这么一来整棵树都连通了,就乱了套了。于是可以把一条无向边拆成两条有向边。

最终边数复杂度\(\mathrm O(m\log n)\),加上Dijkstra的\(\log\),时间复杂度为\(\mathrm O\!\left(m\log^2n\right)\)(由于线段树点数是线性的,所以总点数依然是\(\mathrm O(n)\),只不过一个线段树常数是\(4\),总常数至少有\(16\)/fad)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=100000,M=100000; 
int n,m,s,t;
int a[N+1],b[N+1];
int now;//目前节点数量 
vector<pair<int,int> > nei[18*N+2*M+2];//邻接表 
struct segtree{//优化建图的线段树(4个线段树放一起) 
	struct node{int l,r,nd[4]/*4个线段树分别保存的节点*/;}nd[N<<2];
	#define l(p) nd[p].l
	#define r(p) nd[p].r
	#define nd(p) nd[p].nd
	void bld(int l=1,int r=n,int p=1){//建树 
		l(p)=l;r(p)=r;
		if(l==r)return nd(p)[0]=nd(p)[3]=l,nd(p)[1]=nd(p)[2]=l+n,void();//叶子的节点是实点 
		nd(p)[0]=++now;nd(p)[1]=++now;nd(p)[2]=++now;nd(p)[3]=++now;//非叶子的节点是虚点 
		int mid=l+r>>1;
		bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
		nei[nd(p<<1)[0]].pb(mp(nd(p)[0],0));nei[nd(p<<1|1)[0]].pb(mp(nd(p)[0],0));//出,儿子向爸爸连边 
		nei[nd(p)[1]].pb(mp(nd(p<<1)[1],0));nei[nd(p)[1]].pb(mp(nd(p<<1|1)[1],0));//入,爸爸向儿子连边 
		nei[nd(p<<1)[2]].pb(mp(nd(p)[2],0));nei[nd(p<<1|1)[2]].pb(mp(nd(p)[2],0));//出,儿子向爸爸连边 
		nei[nd(p)[3]].pb(mp(nd(p<<1)[3],0));nei[nd(p)[3]].pb(mp(nd(p<<1|1)[3],0));//入,爸爸向儿子连边 
	}
	void init(){bld();}
	void ae(int l,int r,int id,int v,int len,bool out,int p=1){
		if(l<=l(p)&&r>=r(p))return out?nei[nd(p)[id]].pb(mp(v,len)):nei[v].pb(mp(nd(p)[id],len)),void();//与虚拟节点相连 
		int mid=l(p)+r(p)>>1;
		if(l<=mid)ae(l,r,id,v,len,out,p<<1);
		if(r>mid)ae(l,r,id,v,len,out,p<<1|1);
	}
}segt;
bool vis[18*N+2*M+2];
int dis[18*N+2*M+2];
void dijkstra(){//堆优化Dijkstra 
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
	memset(dis,0x3f,sizeof(dis));
	q.push(mp(dis[now]=0,now));
	while(q.size()){
		int x=q.top().Y;
		q.pop();
		if(vis[x])continue;
		vis[x]=true;
		for(int i=0;i<nei[x].size();i++){
			int y=nei[x][i].X,len=nei[x][i].Y;
			if(dis[x]+len<dis[y])q.push(mp(dis[y]=dis[x]+len,y));
		}
	}
}
signed main(){
	cin>>n>>m>>s>>t;
	now=2*n;
	segt.init();//线段树初始化 
	while(m--){
		int l1,r1,l2,r2,len;
		scanf("%lld%lld%lld%lld%lld",&l1,&r1,&l2,&r2,&len);
		segt.ae(l1,r1,0,++now,len,true);segt.ae(l2,r2,1,now,0,false);//一个方向 
		segt.ae(l2,r2,2,++now,len,true);segt.ae(l1,r1,3,now,0,false);//另一个方向 
	}
	for(int i=1;i<=s;i++)scanf("%lld",a+i);
	for(int i=1;i<=t;i++)scanf("%lld",b+i);
	now++;//单源 
	for(int i=1;i<=t;i++)nei[now].pb(mp(b[i]+n,0)); 
	dijkstra();
	int ans=0;
	for(int i=1;i<=s;i++)ans=max(ans,dis[a[i]]);//答案 
	if(ans>=inf)puts("boring game");
	else cout<<ans;
	return 0;
}

CF::Gym 102174G - 神圣的 F2 连接着我们

标签:lld   保存   直接   node   经典   问题   cin   mat   简单   

原文地址:https://www.cnblogs.com/ycx-akioi/p/CF-Gym-102174G.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!