CF827F Dirty Arkady's Kitchen
一、题目
注意在时间点 \(b\) 是不能通过这条边的,每条边的通行时间是 \([a,b)\)
二、解法
这道题的难点就在于并不是越早到某个点越好,考虑需要较晚到某一个点通路才开放,现有快路径和慢路径,可以先通过快路径到达这个点之后再在慢路径的最后一条边上来回横跳,这样可能达到和慢路径相同的效果。
但是注意如果这两条路径的奇偶性不同那么可能慢路径合法而快路径不合法,所以我们只需要考虑到某个点时间为奇数或为偶数的最小时间,拆成两个点表示不同奇偶性的点。
但是我们还需要知道到达某个点的最晚时间,作为我们是否能通过某个点的判据。两个信息都记录显然不行,我们可以把所有边按出现时间排序,然后按顺序加边,考虑记录 \(f(u,0/1)\) 表示到 \(u\) 最晚的偶数时间 \(/\) 奇数时间,由于 \(f\) 是前面的边扩展所得那么最早到达时间一定小于该边的时间(不会等于,因为扩展之后奇偶性要变),那么只需要判断 \(f(u,0/1)\) 是否大于等于 \(l\) 就知道能不能使用这条边了。
如果不能使用这条边,那么把这条边挂在对应的点上,等 \(f(u,0/1)\) 被更新得更大是再去使用它,由于每条边只会用于更新一次,时间复杂度 \(O(n\log n)\)
三、总结
在有明显的时间戳的图论问题中,动态加边扩展是一个好方法。
所以本题其实是用动态加边解决最小到达时间的问题(你可以理解成维护连通性的常见方法),然后维护最晚到达时间来判定这条边能不能用于扩展,本质就是两种限制的拆分导出了我们使用的方法。
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;
const int M = 500005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,f[M][2];
struct node
{
int u,v,l,r,op;
node(int U=0,int V=0,int L=0,int R=0,int O=0)
: u(U) , v(V) , l(L) , r(R) , op(O) {}
bool operator < (const node &b) const
{
return l>b.l;
}
};priority_queue<node> q;vector<node> g[M][2];
void upd(int u,int v,int l,int r)
{
int op=l&1;
if(f[u][op]>=l)//use the edge now
{
if(v==n) {printf("%d\n",l+1);exit(0);}
if(f[v][!op]<=r+1)
{
f[v][!op]=r+1;
for(node x:g[v][!op])
q.push(node(x.u,x.v,l+1,x.r));
g[v][!op].clear();
}
}
else //put it on the vertex for latter usage
g[u][op].push_back(node(u,v,l,r));
}
signed main()
{
n=read();m=read();
if(n==1) {puts("0");exit(0);}
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),l=read(),r=read()-1;
int op=(r-l)&1;
q.push(node(u,v,l,r-op,1));
q.push(node(u,v,l+1,r-(!op),1));
}
memset(f,-0x3f,sizeof f);f[1][0]=0;
while(!q.empty())
{
node t=q.top();q.pop();
if(t.l>t.r) continue;
upd(t.u,t.v,t.l,t.r);
if(t.op==1) upd(t.v,t.u,t.l,t.r);
}
puts("-1");
}