题解:P8426 [JOI Open 2022] 放学路 / School Road
diandian2020 · · 题解
本题或是广泛作为广义串并联图的练习题也好、或是具有着
最直观的想法是:建出
P.S. 保留所有在
然而这并不充分,因为我们可能“逆行”,即走某条边的反向边。显然只需要找有没有逆行一次的路径即可。下面称一个图合法当且仅当它的答案是
下面直接把最短路图叫做
稍微画一下,感觉稍微复杂一点就能找到:如果所有边为
由此猜测合法的充要条件为
但凡有一条横跨的边就能找到。
所以正确的条件为:找不到当且仅当原图是一个
必要性的说明可以把这个过程反过来,即我们可以通过不然将某个杏仁缩成边来将原图缩成一条边
但是如何动态找到杏仁并删除呢?发现这个就相当于不断将由“出度和入度都为
复杂度
回过头来看广为流传题解的做法,其实感觉非常非人类,就非要对着带权且无向的原图做呗。
#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
const int N=1e5+9,M=4e5+9;
int n,m;
int h[N],e[M],val[M],ne[M],idx=2;
bool on[N];
void add(int a,int b,int c){
e[idx]=b,val[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,val[idx]=c,ne[idx]=h[b],h[b]=idx++;
}
set<int> ou[N],in[N];
int q[N],hh,tt;
void add(int a,int b){
// printf("add %d %d\n",a,b);
ou[a].insert(b),in[b].insert(a);
}
void chk(int i){
if(in[i].size()==1&&ou[i].size()==1&&on[i]) on[i]=0,q[++tt]=i;
}
int dfn[N],low[N],timestamp;
int stk[N],top;
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u;
for(int i=h[u],v;i;i=ne[i]){
if(!dfn[v=e[i]]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]==dfn[u]){
int tmp=top;
bool hv1=u==1,hvn=u==n;
int y;
do{
y=stk[top--];
hv1|=y==1,hvn|=y==n;
}while(y^v);
if(hv1&&hvn){
on[u]=1;
for(int i=top+1;i<=tmp;i++) on[stk[i]]=1;
}
}
}
else low[u]=min(low[u],dfn[v]);
}
}
LL d[N]; bool vis[N];
void Dijkstra(){
priority_queue<PLI,vector<PLI>,greater<PLI>> q;
memset(d,0x3f,sizeof(d));
q.push({d[1]=0,1});
while(!q.empty()){
PLI P=q.top(); q.pop();
int u=P.se;
if(vis[u]) continue;
vis[u]=1;
for(int i=h[u],v;i;i=ne[i]) if(v=e[i],val[i]&&on[v]&&d[u]+val[i]<d[v]) q.push({d[v]=d[u]+val[i],v});
}
}
int main(){
// freopen(".in","r",stdin);
scanf("%d%d",&n,&m);
add(1,n,0);
for(int i=1,a,b,c;i<=m;i++) scanf("%d%d%d",&a,&b,&c),add(a,b,c);
tarjan(1);
// printf("on: "); for(int i=1;i<=n;i++) printf("%d",on[i]); puts("");
Dijkstra();
hh=0,tt=-1;
for(int i=4;i<idx;i+=2){
int a=e[i|1],b=e[i];
if(on[a]&&on[b]){
if(d[a]>d[b]) swap(a,b);
// printf("%d %d %lld %d %lld\n",a,b,d[a],val[i],d[b]);
if(d[a]+val[i]==d[b]) add(a,b);
else return puts("1"),0;
}
}
for(int i=1;i<=n;i++) chk(i);
while(hh<=tt){
int u=q[hh++];
int x=*in[u].begin(),y=*ou[u].begin();
ou[x].erase(u),in[y].erase(u);
add(x,y);
for(int i:{x,y}) chk(i);
}
for(int i=2;i<n;i++) if(on[i]) return puts("1"),0;
puts("0");
return 0;
}