标签:lld 保存 直接 node 经典 问题 cin mat 简单
我知道我现在应该补eJOI题解,但是如果我真的补了,那我不就不是鸽子了么(大雾
题意见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