luoguP1462 通往奥格瑞玛的道路
我们直接二分答案,二分过路费,每次Dij判断走所有小于当前mid的路能否到达终点且最短路(总扣血)小于最大血量。
先dij一下一个极大值,看看能否到达终点。
注意b是血量,而二分的是价格,不是一个东西。
血量是边权, 价格是点权,不要搞混。
数据有可能爆int,应该改成ll。
const int M = 100000, N = 50000;//无向图边开两倍
int n, m;
ll b;//b是起点到终点的最短路径的最大值,也就是说每次dijk求出的最短路径要比这个小才行。可能爆int。
int f[N];//以点定
int h[N], e[M], ne[M], idx, w[M];//头节点以点定,其他以边定
bool st[N];//以点定
ll dist[N];//以点定
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
bool dijk(int x)
{
if (x < f[1])return false;//出发点收费已经大于二分的答案,直接退出,这一步很关键,
//因为后面都是判断某个点价值若不大于当前二分答案x则可以跳过去,但是就忽略了起点的价值到底有没有超过我们的二分答案x
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[1] = 0;
priority_queue < PII, vector<PII>, greater<PII>>heap;
heap.push({ 0,1 });
while (heap.size())
{
auto t = heap.top();
heap.pop();
ll ver = t.second, distance = t.first;//取出这个点的编号和距离,注意要用ll,因为每个点到起点最短距离是可能爆int的!
if (st[ver])continue;//是冗余,则跳过
st[ver] = true;//标记为已确定最短距离
for (int i = h[ver]; i != -1; i = ne[i])//遍历邻边
{
int j = e[i];//取出邻边编号
if (dist[j] > distance + w[i] && f[j] <= x)//看是否要更新邻边,这里关键是f[j] <= x,即时刻保证我们走过的城市价值不大于我们这个二分的答案
{
dist[j] = distance + w[i];
heap.push({ dist[j],j });
}
}
}
if (dist[n] <= b)return true;//说明在这个二分的答案的限制下走过的最短路可以到达终点,则符合条件。
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m >> b;
for (int i = 1; i <= n; i++)
scanf("%d", &f[i]);
while (m--)
{ //读入边,无向图记得存两条
int i, o, u;
scanf("%d%d%d", &i, &o, &u);
add(i, o, u);
add(o, i, u);
}
if (!dijk(1e9))//即在可以随便能走每一条边的情况下的最短路能不能到达,不能的话说明任何情况都走不到终点。
{
cout << "AFK" << endl;
return 0;
}
int l = 1, r = 1e9;//答案只可能在这个区间中,因为f[i]的范围就是这个
while (l < r)//二分答案
{
int mid = (l + r) / 2;
if (dijk(mid))r = mid;//若符合条件,因为我们要找在符合条件下尽量小的,就是尽量往左边找,所以改r=mid
else l = mid + 1;
}
cout << l;
return 0;
}
补充:71到75行可删去,然后定r为1e9+1,然后二分while出来后面写if(l==1e9+1)cout<<AFK
else cout<<l 即可用1e9+1代表最终无解(走不到终点)的情况
Heavy Transportation
题目大意:每条边都有对应的承载货物最大重量;问从1走到n,最大可运送多大的重量货物;
其实就是使1-n联通,想让这些联通的边的最小值尽量大;
法一:二分,每次二分一个重量,>=这个重量的边都可以走,若最后1和n能连通,则让l=mid,否则r=mid-1;
//优dijk+二分;
const int N = 1010, M = 1000010;
int h[N], e[M], ne[M], w[M], idx;
int di[N], vis[N];
int t, n, m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool check(int x)
{
memset(vis, 0, sizeof vis);
memset(di, 0x3f, sizeof di);
priority_queue<I, vector<I>, greater<I> >q;
di[1] = 0;
q.push(make_pair(di[1],1));
while (q.size())
{
I t = q.top();
q.pop();
int ver = t.second, dist = t.first;
if (vis[ver])continue;
vis[ver] = 1;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (di[j] > dist + w[i]&&w[i]>=x)
{
di[j] = dist + w[i];
q.push((make_pair(di[j],j)));
}
}
}
return di[n] != 0x3f3f3f3f;
}
int main()
{
int cnt = 1;
cin >> t;
while (t--)
{
memset(h, -1, sizeof h);
idx = 0;
cin >> n >> m;
while (m--)
{
int a, b, c;
scanf("%d%d%d" ,& a, &b, &c);
add(a, b, c); add(b, a, c);
}
int l = 0, r = 1000000;
while (l < r)
{
int mid = l + r+1 >> 1;
if (check(mid))l = mid;
else r = mid - 1;
}
printf("Scenario #%d:\n%d\n", cnt++, l);
puts("");
}
}
法二:跑最大生成树;即先给边排序,然后依次连边,直到连接某条边之后发现1和n联通了,则这条边的权重一定是我们所要的尽量大的,即答案;
//kuraskal最大生成树
int p[1010];
int n, m, t;
int find(int x)
{
return x == p[x] ? x : p[x] = find(p[x]);
}
struct edge
{
int a, b, c;
bool operator<(const edge& x)const
{
return c > x.c;
}
}ed[2010];
int main()
{
int cnt = 1;
cin >> t;
while (t--)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)p[i] = i;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &ed[i].a, &ed[i].b, &ed[i].c);
}
sort(ed, ed + m);
int ans = 0;
for (int i = 0; i < m; i++)
{
int a = ed[i].a, b = ed[i].b, c = ed[i].c;
if (find(a) != find(b))
{
p[find(a)] = find(b);
}
if (find(1) == find(n))
{
ans = c;
break;
}
}
printf("Scenario #%d:\n%d\n", cnt++, ans);
puts("");
}
}
Easy Glide
本题就是最基本的最短路;麻烦在本题的建图;
给出n(不包括起点和终点)个点的二维坐标;可以用一个数组0 -- n+1存下来,0是起点,n+1是终点,而中间n个点就是滑翔点;
建图的时候我们是将所需要的时间视为跑最短路的dist[],而不是真意义上的距离,题目给我们的点的二维坐标是要我们算两点间距离,然后除一个速度;题给我们v1,v2;v2是代表经过滑翔点后获得维持3s的一个速度;
const int N = 1010,M=2000010;//开1000010会错
I s[N];
int h[N], e[M], ne[M], idx;
double w[M];//开Double
double di[N];//开Double
bool vis[N];
int q[N];
int s1, s2, t1, t2,n;
int v1, v2;
double get(int x1, int y1, int x2, int y2)//取出两点距离
{
return sqrt((double)(x1 - x2) * (x1 - x2) + (double)(y1 - y2) * (y1 - y2));//一定要转double,第一是因为sqrt要求double参数,第二是因为相乘可能爆int范围,转double可以大很多
}
void add(int a,int b,double c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a],h[a]=idx++;
}
void spfa()
{
for (int i = 0; i <= n + 1; i++)di[i] = 1e9;
int hh = 0, tt = 1;
q[0] = 0;
di[0] = 0;
while (hh != tt)
{
int t = q[hh++];
if (hh == N)hh = 0;
vis[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (di[j] > di[t] + w[i])
{
di[j] = di[t] + w[i];
if (!vis[j])
{
vis[j] = 1;
q[tt++] = j;
if (tt == N)tt = 0;
}
}
}
}
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++)
{
cin >> s[i].first >> s[i].second;
}
cin >> s1 >> s2 >> t1 >> t2;
cin >> v1 >> v2;
s[0] = { s1,s2 };//起点
s[n + 1] = { t1,t2 };//终点
for(int i = 0; i <= n + 1; i++)//在所有点之间遍历每一个点对
for (int j = i+1; j <= n + 1; j++)
{
double c = get(s[i].first, s[i].second, s[j].first, s[j].second);//先算出点对的距离
if (!i)//只要是起点,就用不到滑翔点的v2;
{
add(i, j, (double)c / v1);
add(j, i, (double)c / v1);//反边
}
else//是滑翔点
{
if (c / v2 <= 3)//说明3秒的v2没用完就到达了
{
add(i, j, (double)c / v2);
add(j, i, (double)c / v2);
}
else//3秒v2过后还要继续用v1滑
{
add(i, j, (double)(3 + (c - 3 * v2) / v1));
add(j, i, (double)(3 + (c - 3 * v2) / v1));
}
}
}
spfa();
printf("%.12lf", di[n + 1]);
}
补充本题细节:对于浮点数memset只能置0不能无穷,所以必须用for循环一个个赋无穷;
Silver Cow Party
link 题意:单向图,给出一个点,问所有点到这个点的最短路和从这个点回到其他点的最短路,哪个点的答案比较大,输出这个答案;
做法:以这个点正图跑一次单源最短路即可得到从这个点回到其他点的最短路,再以这个点反图跑一次单源最短路即可得到其他点来到这个点的最短路;
const int N = 1010,M=100010;
int h[N], e[M], ne[M],w[M], idx;
int q[N];
int dist[N];
int ans[N];
bool st[N];
int n, m, x;
struct edge
{
int a, b, c;
}ed[M];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa(int x)
{
memset(dist, 0x3f, sizeof dist);
int hh = 0, tt = 1;
q[0] = x;
dist[x] = 0;
st[x] = 1;
while (hh != tt)
{
int t = q[hh++];
if (hh == N)hh = 0;
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q[tt++] = j;
st[j] = 1;
if (tt == N)tt = 0;
}
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m >> x;
for (int i = 0; i < m; i++)//建正图
{
scanf("%d%d%d", &ed[i].a, &ed[i].b,&ed[i].c);
add(ed[i].a, ed[i].b, ed[i].c);
}
spfa(x);//回家的路
for (int i = 1; i <= n; i++)
{
ans[i] += dist[i];
}
memset(h, -1, sizeof h); idx = 0;//初始化整张图
for (int i = 0; i < m; i++)//重新建反图
{
add(ed[i].b, ed[i].a, ed[i].c);
}
spfa(x);//来参加排队的路
int anss = 0;//最终答案
for (int i = 1; i <= n; i++)
{
ans[i] += dist[i];
anss = max(anss, ans[i]);
}
cout << anss;
}
Fire-Fighting Hero
显然对于英雄来说直接跑一个单源最短路即可
对于消防员而言,可以新建一个超级源点,跟其他出发点连上边权为0 00的边,也同样跑一遍单源最短路即可;注意平局也算是英雄胜利;(考虑对k个点进行缩点, 增加一个超级源点0, 双向连接k个点, 权值为0, 这样就可以快速求出k个点到其他点的最短路径
注意要先求出s点的最短路径再加点, 因为加点会影响s点的最短路径.)
int n, m, s, k, c;
const int N = 1010, M = 1002000;
int h[N], e[M], ne[M], w[M], idx;
int d[N];
int dist[N];
int st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijk(int x)
{
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
dist[x] = 0;
priority_queue<PII, vector<PII>, greater<PII>>q;
q.push({ dist[x],x });
while (q.size())
{
auto t = q.top();
q.pop();
int ver = t.second, distance = t.first;
if (st[ver])continue;
st[ver] = 1;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
q.push({ dist[j],j });
}
}
}
}
int main()
{
int t; cin >> t;
while (t--)
{
memset(h, -1, sizeof h); idx = 0;
cin >> n >> m >> s >> k >> c;
for (int i = 0; i < k; i++)scanf("%d", &d[i]);
while (m--)
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
int ans1, ans2;
dijk(s);
for (int i = 1; i <= n; i++)ans1 = max(ans1, dist[i]);
for (int i = 0; i < k; i++)//注意要先求出s点的最短路径再加点, 因为加点会影响s点的最短路径.
{
add(n + 1, d[i], 0);
add(d[i], n + 1, 0);
}
dijk(n + 1);//把n+1这个点当作虚拟原点与所有起点联,跑单源dijk;
for (int i = 1; i <= n; i++)ans2 = max(ans2, dist[i]);
if (ans2 * c > ans1)cout << ans1 << endl;
else cout << ans2 << endl;
}
}
Counting Shortcuts
link 一道有关最短路计数和次短路计数
题目大意:
给定一张无自环无重边的无向图G,以及起点和终点S和T,边权都为1,求从S到T的路径长度<=最短路长度+1的数量。
解题思路
由于边权长度恒定为1,因此最短路计数十分简单,利用bfs即可,前时间复杂度为稳定的O(2m)。无需使用spfa或者djstra。
当我们获得最短路的数量后,我们就需要考虑对于某一点如何求其最短路+1的路径数量。
对于点x,设其距离S的最短路长度为midis,有哪些点会更新该点的最短路+1的数量。
假设点x与点y联通:
当点y的dis>midis时,y无法为x做出贡献,这是由于X的dis已经最短,假如采取从y到x的策略,那么x就是dis+1,这已然比最短路长了2个距离。
当dis=midis时,只比最短路大1,这是我们需要的
当dis==midis-1时,x点是可以继承y点的最短路+1的路径数的。
当dis<midis-1时,显然无贡献。
根据该特性,我们可以看出状态更新具有一定的拓扑序,因此我们可以按照距离从小到大的方式递推更新状态。
进一步分析,最短路长度为x的点,会被哪些点更新?
可以被划分为两个集合:
最短路长度 == x-1 的点,(继承)
最短路长度 == x 的点,(增加)
那么是先继承还是先增加,还是都无影响呢?
继承的是上一个结点的最短路+1的数量,这意味这上一结点的最短路数量需要是最终状态。
增加的是上一个结点最短路的数量,该状态再最初的bfs已经完成了。
于是先增加再继承便可将所有的状态转移。
const int mod = 1e9 + 7,N=200010;
int h[N], e[2*N], ne[2*N],idx;
int n, m, s, t;
int dist[N];
int dp[2][N];//0是最短路的数目,1是次短路
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs()//bfs做一遍最短路计数
{
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
dp[0][s] = 1;
queue<int>q;
q.push(s);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + 1)
{
dp[0][j] = dp[0][t];
dist[j] = dist[t] + 1;
q.push(j);
}
else if (dist[j] == dist[t] + 1)dp[0][j] += dp[0][t],dp[0][j]%=mod;//+=的地方一定要取mod;
}
}
}
int main()
{
int tt; cin >> tt;
while (tt--)
{
memset(dp, 0, sizeof dp);
memset(h, -1, sizeof h), idx = 0;
cin >> n >>m >> s >> t;
while (m--)
{
int a, b; scanf("%d%d", &a, &b);
add(a, b); add(b, a);
}
bfs();
vector<PII>sx;
for (int i = 1; i <= n; i++)sx.push_back({ dist[i],i });//必须按每个点的距离排序他们,从最近的距离的点开始dp
sort(sx.begin(), sx.end());
//下面这两层for循环不能合二为一
for (int i = 0; i < n; i++)
{
int x = sx[i].second;
for (int i = h[x]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] == dist[x])dp[1][j] += dp[0][x],dp[1][j]%=mod;//必须先算完这个
}
}
for (int i = 0; i < n; i++)
{
int x = sx[i].second;
for (int i = h[x]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] == dist[x]+1)dp[1][j] += dp[1][x], dp[1][j] %= mod;//再算这个
}
}
cout << (dp[1][t] + dp[0][t]) % mod << endl;
}
}
天梯地图
int n, m;
const int N = 510, M = 250010;
int h[N], e[M], ne[M], w[M], t[M], idx;//w[]代表距离为权重 t[]代表时间为权重
int qd, zd;
int dist[N];//最短路
bool st[N];
int pre1[N];//存以距离为权重跑的最短路的答案路径上的每一个前继点
int pre2[N];//存以时间为权重跑的最短路的答案路径上的每一个前继点
int ver[N];//在以距离为权重跑的最短路下所需要记录路径一共经历了多少个点(因为存在相同距离的答案路径时要选择经过点少的那一条;
int dis[N];//在以时间为权重跑的最短路下所需要记录路径一共经历了多少距离(因为存在相同时间的答案路径时要选择经过距离少的那一条;
void dfspath(int u, int pre[])//dfs输出答案路径
{
if (u == qd)
{
printf(" %d =>", u); return;
}
dfspath(pre[u], pre);//先调用递归,后输出当前节点;
if (u != zd)printf(" %d =>", u);
else printf(" %d\n", u);
}
bool samepath(int u, int v)//判断以距离为权重所跑的最短路和以时间为权重所跑的最短路是否是同一条路径
{
if (u == qd)return 1;//到了qd则说明是同一条路径
if (u != v)return 0;//若有不同的点则说明不是同一条;
return samepath(pre1[u], pre2[v]);//以前继点继续递归;
}
void add(int a, int b, int c, int d)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, t[idx] = d, h[a] = idx++;
}
int spfadist(int qd, int zd)//以距离为权重所跑的最短路
{
memset(dist, 0x3f, sizeof dist);
queue<int> q;
dist[qd] = 0;
q.push(qd);
while (q.size())
{
int f = q.front();
q.pop();
st[f] = 0;
for (int i = h[f]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[f] + w[i])
{
dist[j] = dist[f] + w[i];
pre1[j] = f;//记录前继点
ver[j] = ver[f] + 1;//经过的点数+1;
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
else if (dist[j] == dist[f] + w[i] && ver[f] + 1 < ver[j])//距离相等且这条新路走过的节点更少时
{
pre1[j] = f;//更换新的前继点
ver[j] = ver[f] + 1;//更换新的经过的点数
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
}
}
return dist[zd];
}
int spfatime(int qd, int zd)//以时间为权重所跑的最短路
{
memset(dist, 0x3f, sizeof dist);
queue<int> q;
dist[qd] = 0;
q.push(qd);
while (q.size())
{
int f = q.front();
q.pop();
st[f] = 0;
for (int i = h[f]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[f] + t[i])
{
dist[j] = dist[f] + t[i];
pre2[j] = f;//记录前继点
dis[j] = dis[f] + w[i];//更新距离
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
else if (dist[j] == dist[f] + t[i] && dis[f] + w[i] < dis[j])//经过时间相同但是这条新路有着更短的距离时
{
pre2[j] = f;//更换新的前继点
dis[j] = dis[f] + w[i];//更换新的距离更短的距离
if (!st[j])
{
q.push(j);
st[j] = 1;
}
}
}
}
return dist[zd];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m--)
{
int a, b, c, d, e;
scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);
add(a, b, d, e);
if (c == 0)add(b, a, d, e);
}
cin >> qd >> zd;
spfatime(qd, zd); spfadist(qd, zd);
if (samepath(zd, zd))//判断是否是同一条路径
{
printf("Time = %d; Distance = %d:", spfatime(qd, zd), spfadist(qd, zd));
dfspath(zd, pre1);
}
else
{
printf("Time = %d:", spfatime(qd, zd));
dfspath(zd, pre2);
printf("Distance = %d:", spfadist(qd, zd));
dfspath(zd, pre1);
}
}