拓扑排序+迪杰斯特拉:https://www.acwing.com/problem/content/344/
首先我们可以观察到:
1.整个图的形状是一个个通过道路相连的团,而团与团之间通过航线相连。
2.注意到团的非负性,可以用迪杰斯特拉,考虑到航线的单向性,我们可以考虑拓扑排序。
具体的,我们把他们融合在一起:
1.先输入道路扫出连通块。
2.再输入航线确定入度
3.按照拓扑序处理每一个连通块,当其中一个连通块入度为0时取出最小的放入队列中。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
int t,r,p,s;
int id[250100];
int inf=0x3f3f3f3f;
struct node{
int dian,w;
};
vector<int> block[250100];
vector<node> edge[250100];
int cnt=0;
int deg[250100];
int dis[250100];
int st[250100];
void dfs(int x,int c){
id[x]=c;
block[c].push_back(x);
for(int i=0;i<edge[x].size();i++){
int ck=edge[x][i].dian;
if(id[ck]) continue;
dfs(ck,c);
}
}
void dij(){
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;//(距离,编号)
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
for(int i=1;i<=cnt;i++){
if(deg[i]) continue;
for(int j=0;j<block[i].size();j++){
int x=block[i][j];
q.push({dis[x],x});
}
}
while(!q.empty()){
auto ck=q.top();
q.pop();
if(st[ck.y]) continue;
st[ck.y]=1;
for(int i=0;i<edge[ck.y].size();i++){
auto fk=edge[ck.y][i];
if(dis[fk.dian]>dis[ck.y]+fk.w){
dis[fk.dian]=dis[ck.y]+fk.w;
if(id[fk.dian]==id[ck.y]) q.push({dis[fk.dian],fk.dian});
}
}
for(int i=0;i<edge[ck.y].size();i++){
auto fk=edge[ck.y][i];
if(id[fk.dian]!=id[ck.y]){
deg[id[fk.dian]]--;
if(deg[id[fk.dian]]==0){
for(int j=0;j<block[id[fk.dian]].size();j++){
int x=block[id[fk.dian]][j];
q.push({dis[x],x});
}
}
}
}
}
}
int main(){
cin>>t>>r>>p>>s;
for(int i=1;i<=r;i++){
int a,b,c;
cin>>a>>b>>c;
edge[a].push_back({b,c});
edge[b].push_back({a,c});
}
//找连通块
for(int i=1;i<=t;i++){
if(id[i]==0) dfs(i,++cnt);
}
//加入航线
for(int i=1;i<=p;i++){
int a,b,c;
cin>>a>>b>>c;
edge[a].push_back({b,c});
deg[id[b]]++;
}
//dij
dij();
for(int i=1;i<=t;i++){
if(dis[i]>=inf/2) cout<<"NO PATH"<<endl;
else cout<<dis[i]<<endl;
}
}
二分+单源最短路:
首先二分出来一个x,我们想知道一条路上至少有几个是大于X的。我们每一次可以把边权大于X的看成1,比他小的看成0,于是问题就转换成了求单源点的最短路。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 20010;
int n, m, k;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
deque<int> q;
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(int bound)
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
q.push_back(1);
dist[1] = 0;
while (q.size())
{
int t = q.front();
q.pop_front();
if (st[t]) continue;
st[t] = true;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i], x = w[i] > bound;
if (dist[j] > dist[t] + x)
{
dist[j] = dist[t] + x;
if (!x) q.push_front(j);
else q.push_back(j);
}
}
}
return dist[n] <= k;
}
int main()
{
cin >> n >> m >> k;
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
int l = 0, r = 1e6 + 1;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (r == 1e6 + 1) cout << -1 << endl;
else cout << r << endl;
}
DP+最短路:https://www.acwing.com/problem/content/343/
可以观察到:
从源点1到终点n一定存在一个分界点i,在上取到最小值,在
上取到最大值。
于是我们记表示
上的最大值,
表示
上的最小值,然后我们枚举每一个
即可。
现在我们主要求这个数组,假如图是严格的拓扑序,那么DP转移就十分明显,但是现在的图可能存在回路,直接DP会陷入死循环,于是我们考虑最短路的思想。
我们把距离的概念抽象成上面的两个数组的值,松弛操作也就是
注意虽然求单源最短路可以迪杰斯特拉,但是他的前提是每一次第一出队的一定是最短的,而显然这个条件在这里不成立(因为后面的可能比当前出对的更小)。
那么SPFA?它的本质是动态规划,每一次在前一次的基础上松弛,保证了走到某一点在i步范围内是正确的,进而不断转移到n步,(这在没有负环的情况下显然是包含所有情况)而在本题中不断走一个环没有意义,也就是不存在负环。因此SPFA是适用的。
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100010];
vector<int> edge[500010];
vector<int> edeg[500010];
int maxx[100010],minn[100010];
int st[100010];
void spfa(int type)
{
queue<int> q;
memset(st, 0, sizeof st);
if(type==0){
memset(minn, 0x3f, sizeof(minn));
minn[1] = a[1];
q.push(1);
st[1] = true;
}
else{
memset(maxx, 0xc0, sizeof(maxx));
maxx[n]=a[n];
q.push(n);
st[n]=1;
}
while (!q.empty())
{
int t = q.front();
st[t] = false;
q.pop();
if(type==0){
for (int i =0; i <edge[t].size(); i ++)
{
int j = edge[t][i];
if (minn[j] > min(minn[t],a[j]))
{
minn[j] = min(minn[t],a[j]);
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}}
else{
for (int i =0; i <edeg[t].size(); i ++){
int j=edeg[t][i];
if(maxx[j]<max(maxx[t],a[j])){
maxx[j]=max(maxx[t],a[j]);
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
edge[x].push_back(y);
edeg[y].push_back(x);
if(z==2){
edge[y].push_back(x);
edeg[x].push_back(y);
}
}
spfa(0);
spfa(1);
int res=0;
for(int i=1;i<=n;i++) res=max(res,maxx[i]-minn[i]);
cout<<res;
}