USACO11JAN 道路和飞机 Roads and Planes

思路

这题要求的是带负权边的最短路,显然不能直接用Dijkstra。然而Bellman-Ford或SPFA的时间复杂度最坏$O(NM)$,而且众所周知,USACO总是卡SPFA的。尽管这题数据比较老,因此SPFA+SLF可以水过,但是正解并不是如此。

顺便说一下,用优先队列优化SPFA并不有效(来自[模板]单源最短路-题解),在这题比普通SPFA更慢。然而在某些边权为正的卡SPFA的图中,几乎和Dijkstra不相上下。

可以发现,图有一个很强的性质:对于无向边,边权总是正的,因此每个无向边联通块是强联通的。而可能的负权边保证不能反向联通,因此把无向边联通块缩点后,得到的就是DAG。这样就可以在块内使用Dijkstra,块间利用拓扑排序更新答案。时间复杂度$O(M\log N)$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include<bits/stdc++.h>
using namespace std;
const int N=25005,INF=0x3f3f3f3f;
typedef pair<int,int> pii;
vector<pii> roads[N],planes[N];
vector<int> comp[N];
//联通分量中的点
int belong[N],indeg[N],d[N];
//belong[]表示每个点所属的联通分量,indeg[]表示联通分量的入度
bool vis[N];
void dfs(int k,int num)
{
belong[k]=num;
comp[num].push_back(k);
for(auto e:roads[k])
if(!belong[e.first])
dfs(e.first,num);
}
//dfs洪水填充
int main()
{
int n,m,p,s;
cin>>n>>m>>p>>s;
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
roads[u].push_back(make_pair(v,w));
roads[v].push_back(make_pair(u,w));
}
while(p--)
{
int u,v,w;
cin>>u>>v>>w;
planes[u].push_back(make_pair(v,w));
}
int c=0;
for(int i=1;i<=n;i++)
if(!belong[i])
dfs(i,++c);
for(int i=1;i<=n;i++)
for(auto e:planes[i])
indeg[belong[e.first]]++;
fill(d+1,d+n+1,INF);
d[s]=0;
queue<int> Q;
for(int i=1;i<=c;i++)
if(!indeg[i])
Q.push(i);
while(!Q.empty())
{
int k=Q.front();Q.pop();
priority_queue<pii,vector<pii>,greater<pii>> PQ;
for(auto i:comp[k])
if(d[i]<INF)
PQ.push(make_pair(d[i],i));
while(!PQ.empty())
{
pii k=PQ.top();PQ.pop();
if(vis[k.second])
continue;
vis[k.second]=true;
for(auto e:roads[k.second])
if(d[k.second]+e.second<d[e.first])
PQ.push(make_pair(d[e.first]=d[k.second]+e.second,e.first));
for(auto e:planes[k.second])
d[e.first]=min(d[e.first],d[k.second]+e.second);
}
for(auto i:comp[k])
for(auto e:planes[i])
if(--indeg[belong[e.first]]==0)
Q.push(belong[e.first]);
}
//拓扑排序和Dijkstra
for(int i=1;i<=n;i++)
if(d[i]==INF)
cout<<"NO PATH\n";
else
cout<<d[i]<<endl;
return 0;
}

拓展:生成数据

这题很有趣,因此我思考了如何生成数据。直接随机显然不符合题意,需要一定的构造。我的方法是:可以先确定每个点所属的连通分量,然后在连通分量中生成树保证连通,再在连通分量内随机添加边。然后在连通分量间生成树保证连通,再随机加边。只要规模足够大,用这种方法生成的数据足以卡掉SPFA了。

附代码供参考(C++11):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<bits/stdc++.h>
using namespace std;
ofstream fout("roadplane.in");
const int n=100000,m=300000,w=50000,cn=1000,W=100000;
int belong[n+5];
vector<int> comp[cn+5];
int f[n+5];
int getf(int x)
{
return f[x]==x?x:f[x]=getf(f[x]);
}
int main()
{
minstd_rand gen(time(NULL));
for(int i=1;i<=n;i++)
{
uniform_int_distribution<> dc(1,cn);
belong[i]=dc(gen);
comp[belong[i]].push_back(i);
}
assert(comp[1].size());
uniform_int_distribution<> d(0,comp[1].size()-1);
fout<<n<<' '<<m<<' '<<w<<' '<<comp[1][d(gen)]<<endl;
for(int i=1;i<=cn;i++)
{
int cnt=0;
for(int j=0;j<comp[i].size();j++)
f[j]=j;
while(cnt+1<comp[i].size())
{
uniform_int_distribution<> did(0,comp[i].size()-1);
int u=did(gen),v=did(gen),ru=getf(u),rv=getf(v);
if(ru!=rv)
{
f[ru]=rv;
cnt++;
uniform_int_distribution<> dw(0,W);
fout<<comp[i][u]<<' '<<comp[i][v]<<' '<<dw(gen)<<endl;
}
}
}
for(int i=n-cn+1;i<=m;i++)
{
uniform_int_distribution<> dc(1,cn);
int c=dc(gen);
if(!comp[c].size())
{
i--;
continue;
}
uniform_int_distribution<> did(0,comp[c].size()-1),dw(0,W);
fout<<comp[c][did(gen)]<<' '<<comp[c][did(gen)]<<' '<<dw(gen)<<endl;
}
for(int i=1;i<=cn;i++)
f[i]=i;
for(int i=1;i<cn;)
{
uniform_int_distribution<> dc(1,cn-1);
int cu=dc(gen);
uniform_int_distribution<> dc1(cu+1,cn);
int cv=dc1(gen),rcu=getf(cu),rcv=getf(cv);
if(rcu==rcv)
continue;
f[rcu]=rcv;
if(!comp[cu].size()||!comp[cv].size())
continue;
uniform_int_distribution<> didu(0,comp[cu].size()-1),didv(0,comp[cv].size()-1),dw(-W,W);
fout<<comp[cu][didu(gen)]<<' '<<comp[cv][didv(gen)]<<' '<<dw(gen)<<endl;
i++;
}
for(int i=cn;i<=w;)
{
uniform_int_distribution<> dc(1,cn-1);
int cu=dc(gen);
uniform_int_distribution<> dc1(cu+1,cn);
int cv=dc1(gen);
if(!comp[cu].size()||!comp[cv].size())
continue;
uniform_int_distribution<> didu(0,comp[cu].size()-1),didv(0,comp[cv].size()-1),dw(-W,W);
fout<<comp[cu][didu(gen)]<<' '<<comp[cv][didv(gen)]<<' '<<dw(gen)<<endl;
i++;
}
return 0;
}