题解 P2296 【寻找道路】
这题超级锻炼思维的!最好是能自己想一想做出来!
如果你实在不会分析,我就来说说我做这道题是怎么从10分到AC想的吧(:з」∠)
对于题目的两个要求我们可以做如下分析:
-
路径上的所有点的出边所指向的点都直接或间接与终点连通。
这句话乍一看不好理解,但是观察样例解释我们可以发现,这个限制的实质就是:删除不与终点连通的点,以及与他们连一条边的点。
- 删除这个操作我们常用打标记的方法,在枚举图的时候遇到没打标记的点就跳过即可。
- 删除不与终点连通的点,用通俗的话讲就是你倒着从终点出发,把图搜完都到不了的点。倒着从终点出发可以直接反向存边,暴力搜索,给搜索到的点打上标记即可
- 对于没搜到的点,如何删去他们连边的点呢?依然用暴力的想法(O(10000)复杂度),枚举所有没打标记的点,枚举他们的连边消去他们的标记不就好了吗?
至此,第一个要求便分析完毕,但是有一个细节要注意:
不能直接消去第一次打上的标记!!你在第二次删除标记时是可能有后效性的,即删去一个与不与终点连接的点后,下一次搜到那个点他还会做一遍删除操作,即把相隔两条边的点给去掉了,与题不符!!
于是重新备份一下标记,用那个备份标记判断即可。感谢大佬提醒
- 在满足条件 1 的情况下使路径最短。
这个很好理解,跳过没有标记的点跑一边最短路即可。因为你一开始存边是倒着存的,其实你不必重新存一遍,直接从终点出发,找到起点的最短路即可。两者应该是等价的。(:з」∠)
最后上我丑陋的代码:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct ed{
int u;
int nex;
}e[300000];
int n,m,x,y,d[300000],fir[20000],st,s,t;
bool v[20000],ok[20000],vis[20000];
void add(int u,int v)
{
e[++st].u=u; e[st].nex=fir[v]; fir[v]=st;
}
void spfa(int b) //最短路
{
memset(v,0,sizeof(v));
memset(d,0x3f3f,sizeof(d));
queue<int> q; q.push(b); v[b]=1; d[b]=0;
while (!q.empty())
{
int k=q.front(); q.pop(); v[b]=0;
for (int i=fir[k];i;i=e[i].nex)
{
int u=e[i].u,w=1;
if (d[u]>d[k]+w&&ok[e[i].u]) {
d[u]=d[k]+w;
if (!v[u]) v[u]=1,q.push(u);
}
}
}
}
void bfs(int t) //第一次的搜边
{
memset(v,0,sizeof(v));
queue<int> q; q.push(t); ok[t]=v[t]=1;
while (!q.empty())
{
int k=q.front(); q.pop();
for (int i=fir[k];i;i=e[i].nex)
if (!v[e[i].u]){
int u=e[i].u; if (!v[u]) v[u]=1,ok[u]=1,q.push(u);
}
}
}
int main()
{
cin>>n>>m;
for (int i=1;i<=m;i++) cin>>x>>y,add(x,y);
cin>>s>>t;
bfs(t);
for (int i=1;i<=n;i++) vis[i]=ok[i];
for (int i=1;i<=n;i++)
if (!vis[i]) for (int j=fir[i];j;j=e[j].nex){
if (ok[e[j].u]) ok[e[j].u]=0;
// 不能直接修改,有后效性。
}
spfa(t);
if (d[s]>=(0x3f3f)) cout<<"-1"<<endl;
else cout<<d[s]<<endl;
retrun 0;
}
各位把关键部分看看就好,如代码有错误那一定是我故意的(:з」∠)