1135. 新年好(活动 - AcWing)
这里有五个点需要被遍历到,要求遍历完五个点总路程的最小值,那么首先的问题就是确定这五个点的遍历顺序,显然我们需要dfs一下,然后我们还需要知道任意两点之间距离的最小值,很容易想到floyd算法,但是这道题n的范围有些大,并不支持floyd算法,但我们注意到我们只用知道这6个点之间的关系就可,那么我们可以在每个点的位置做一次单源最短路,这样的话时间复杂度是支持的。然后还有一个小问题,我们是预处理出来,还是每次确定一种顺序了就分别求一次最短路。当然是预处理更优,那么问题实际就解决了,预处理以这六个点为起点的最短路,然后dfs找顺序求值,然后这道题就解决了。
至于最短路算法,这里既可以用spfa也可以用堆优化的dijkstra,但实际上数据会把spfa卡掉,那么换dijkstra即可。
#include<bits/stdc++.h>
using namespace std;
const int N=50010,M=200010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int d[6][N],st[N],id[10];
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
typedef pair<int,int> pii;
void spfa(int u)
{
memset(d[u],0x3f,sizeof d[u]);
memset(st,0,sizeof st);
priority_queue<pii,vector<pii>,greater<pii>>q;
d[u][id[u]]=0;
q.push({0,id[u]});
while(q.size())
{
auto t=q.top();
q.pop();
int dist=t.first,v=t.second;
if(st[v]) continue;
st[v]=1;
for(int i=h[v];i!=-1;i=ne[i])
{
int j=e[i];
if(d[u][j]>d[u][v]+w[i])
{
d[u][j]=d[u][v]+w[i];
q.push({d[u][j],j});
}
}
}
}
int vis[10];
vector<int>q;
int ans=0;
void dfs(int k)
{
if(k==5)
{
int last=0,res=0;
for(auto it:q)
{
res += d[last][id[it]];
last=it;
}
ans=min(ans,res);
return;
}
for(int i=1;i<=5;i++)
{
if(!vis[i])
{
vis[i]=1;
q.push_back(i);
dfs(k+1);
q.pop_back();
vis[i]=0;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
id[0]=1;
for(int i=1;i<=5;i++) scanf("%d",&id[i]);
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
for(int i=0;i<=5;i++) spfa(i);
ans=0x3f3f3f3f;
dfs(0);
cout<<ans;
}
ps:这里有一点值得注意,就是d[][]的第一维对应的是车站的映射,而非车站,这个要理清楚。
340. 通信线路(340. 通信线路 - AcWing题库)
我们再进一步分析一下题意,定义一条从起点到终点得路径得长度为路径中第k+1大的边,如果边数没有这么多,那么就将这条路径的长度定义为0。求在这种情况下,从起点到终点的最小距离。
这里实际上可以视为求最大值最小的题,可以用二分来写。我们对结果进行二分,然后就要去对二分出来的结果进行验证,问题就转化成如何验证了,验证的条件找出一个性质,使得以答案为分界线,一边满足,一边不满足。这里我们可以将大于二分值的边的边权视为1,小于等于它的视为0,然后看能否找到一条路径使得大于二分值的边的数量小于等于k,如果不成立,那么很显然,二分值应该再取大一点,如果成立那么这个点就可能为答案,当然,它可以继续往小的找,是符合要求的。
然后既然这里的边权只剩0和1,那么实际上可以用双端bfs实现,这个的时间复杂度是线性的。
#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=20010;
int n,m,k;
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int d[N],st[N];
int check(int mid)
{
memset(d,0x3f,sizeof d);
memset(st,0,sizeof st);
deque<int>q;
q.push_back(1);
d[1]=0;
while(q.size())
{
auto t=q.front();
q.pop_front();
if(st[t]) continue;
st[t]=1;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
int x=w[i]>mid;
if(d[j]>d[t]+x)
{
d[j]=d[t]+x;
if(!x) q.push_front(j);
else q.push_back(j);
}
}
}
return d[n]<=k;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
int l=0,r=1000001;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
if(l!=1000001)cout<<l;
else cout<<-1;
}
342. 道路与航线(活动 - AcWing)
这里花费c有负数的情况,那么很显只能用spfa,但是这题用spfa会被卡。那么我们现在必须想其他的办法。这里有三个很关键的条件,道路是双向的;如果有航线从A到B,那么无论是通过道路还是航线都不能从B到A;道路的边权都是正的,航线的边权都是负的。
我们依次来看,很显然如果只考虑道路我们可以用任何一种最短路算法,因为道路的边权都是正的。然后如果A,B之间有一条航线,那么就相当于把A,B所在的与A,B可以互达的点分隔开,因为可以从A到B,就没有任何办法可以从B到A,有向无环,很容易想到拓扑图。我们将A,B各自所在的由无向边组成的单元视为一个点的话,那么实际上就可以通过航线得到一张拓扑图。关于拓扑图,我们可以用线性的时间复杂度完成遍历。那么这里实际上就有一个思路了,我们在每个无向边构成的独立集合内部使用堆优化的dijkstra,在各个独立集合之间使用拓扑图的遍历。
然后就要考虑如何实现,
1.首先按照输入我们应该先建立无向边
2.建好之后我们先通过dfs将每个连通块打上不同的标记。
3.然后开始建立有向边,同时将不同标记对应的集合的入度统计出来。
4.然后开始拓扑排序算法,将入度为0的集合放入队列
5.对于入度为0的集合进行dijkstra算法,在dijkstra算法中,如果访问到标记不同的两点,那么就将指向的那个点的入度减1,同时判断是否需要入队(拓扑排序的队列,这里定义一个全局变量);然后距离正常更新,如果两点标记相同,也即在一个集合当中,那么就将被更新的点放入优先队列中。
6.最后遍历判断所有的点是否都被更新成有效距离,这里要注意,因为有负权,所以即使没有被有效更新可能还是被更新了。
#include<bits/stdc++.h>
using namespace std;
const int N=25010,M=150010,inf=0x3f3f3f3f;
int n,mr,mp,s;
int h[N],e[M],ne[M],w[M],idx;
int d[N],st[N],id[N],rd[N];
int bcnt;
queue<int>q;
vector<int>block[N];
typedef pair<int,int> pii;
void add(int a,int b,int c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int cnt)
{
id[u]=cnt;
block[cnt].push_back(u);
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!id[j])
dfs(j,cnt);
}
}
void dijkstra(int u)
{
priority_queue<pii,vector<pii>,greater<pii>>p;
for(auto it:block[u])
p.push({d[it],it});
while(p.size())
{
auto t=p.top();
p.pop();
int dist=t.first,v=t.second;
if(st[v]) continue;
st[v]=1;
for(int i=h[v];i!=-1;i=ne[i])
{
int j=e[i];
if(id[j]!=id[v]&&--rd[id[j]]==0) q.push(id[j]);
if(d[j]>dist+w[i])
{
d[j]=dist+w[i];
if(id[j]==id[v]) p.push({d[j],j});
}
}
}
}
void topsort()
{
memset(d,0x3f,sizeof d);
d[s]=0;
for(int i=1;i<=bcnt;i++)
if(!rd[i])
q.push(i);
while(q.size())
{
auto t=q.front();
q.pop();
dijkstra(t);
}
}
int main()
{
scanf("%d%d%d%d",&n,&mr,&mp,&s);
memset(h,-1,sizeof h);
while(mr--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
for(int i=1;i<=n;i++)
{
if(!id[i])
{
bcnt++;
dfs(i,bcnt);//集合的下标从1开始
}
}
while(mp--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
rd[id[b]]++;//入度
add(a,b,c);
}
topsort();
int flag=1,res=0;
for(int i=1;i<=n;i++)
{
if(d[i]>=inf/2) cout<<"NO PATH"<<endl;
else cout<<d[i]<<endl;
}
}
341. 最优贸易(341. 最优贸易 - AcWing题库)
思路:这道题虽然是图论的问题,但是可以结合dp来考虑。我们将每个点k视为分界,计算出在k前面买的最小值dmin和在k后面卖的最大值dmax,那么dmax-dmin就是以k点为分界的获利。这里因为第一次出队时未必是最终的答案,比如最大值,有可能出现有环的情况,那么我们用spfa算法来求解。注意这里需要维护的不再是和,而是最大值与最小值。
然后还有一个问题,可能会质疑最大值出现在最小值前面的情况,但是我们并不是将第一次出队时的值就视为答案,如果是双向边,那么前面的也可以更新后面的。
实现:因为这里需要从前往后又需要从后往前找,所以势必要建立一张反向图,那么这个反向图该如何建呢?因为这里有单向边,所以我们要明确,反向图表示的是可以从i到n,还是可以从n到i,显然应该是可以从i到n,因为只要可以从i到n就可以被统计入结果,如果不可以,那么结果就不能包含这个。反向图是从n开始搜的,如果搜到i说明i可以到n,为了搜到i,肯定是建立从n到i的路径,也即将单向边反向来建。
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=2000010,inf=0x3f3f3f3f;
int hs[N],ht[N],e[M],ne[M],idx;
int dmi[N],dmx[N];
int st[N];
int n,m,p[N];
void add(int h[],int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int h[],int d[],int flag)
{
queue<int>q;
if(!flag)
{
//memset(d,0x3f,sizeof d);
for(int i=1;i<=n;i++) d[i]=inf;
q.push(1);
d[1]=p[1];
}
else
{
//memset(d,-0x3f,sizeof d);
for(int i=1;i<=n;i++) d[i]=-inf;
q.push(n);
d[n]=p[n];
}
while(q.size())
{
auto t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if (flag == 0 && d[j] > min(d[t], p[j]) || flag == 1 && d[j] < max(d[t], p[j]))
{
if (flag == 0) d[j] = min(d[t], p[j]);
else d[j] = max(d[t], p[j]);
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
memset(hs,-1,sizeof hs);
memset(ht,-1,sizeof ht);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(hs,a,b),add(ht,b,a);
if(c==2) add(hs,b,a),add(ht,a,b);
}
spfa(hs,dmi,0);
spfa(ht,dmx,1);
int mx=0;
for(int i=1;i<=n;i++)
{
mx=max(mx,dmx[i]-dmi[i]);
}
cout<<mx;
}