最短路
\(\text{poj-1062}\)
有 \(n\) 个商品,每个商品可以直接购买,也可以用其他商品加点钱兑换,每个商品有一个等级。
求最少需要多少钱才能得到第一个商品(过程中参与的商品等级差不超过 \(m\))。
\(1 \le n \le 100\)。
建立一个超级源点 \(0\),从 \(0\) 建立一条边到每个物品,边权为每个物品的价值。若某个物品 \(X\) 有替代品,等价于建立一条有向边:替代品 \(\to\) 物品 \(X\),边权为物品 \(X\) 用该替代品折扣后的价格。
若此时没有等级的限制,求的是从花费的金额的最小值是多少,那么我们可以直接从虚拟源点做一遍\(\text{dijkstra}\) 算法,然后返回 \(dis_1\);
考虑等级限制,限制范围即可 \([a_1 - m, a_1]\)。而任何一个位于合法区间里的点,都有可能更新,而我们也只更新合法区间里的点,所以要枚举所有的合法区间。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, w[MAXN][MAXN], a[MAXN], dis[MAXN];
bool vis[MAXN];
long long dijkstra(long long l, long long r) {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
dis[0] = 0;
for(int i = 0; i < n; i ++) {
long long t = -1;
for(int j = 0; j <= n; j ++)
if(!vis[j] && (t == -1 || dis[t] > dis[j])) t = j;
vis[t] = true;
for(int j = 1; j <= n; j ++) if(a[j] >= l && a[j] <= r)
dis[j] = min(dis[j], dis[t] + w[t][j]);
}
return dis[1];
}
int main() {
m = read(), n = read();
memset(w, 0x3f, sizeof w);
for(int i = 1; i <= n; i ++) w[i][i] = 0;
for(int i = 1; i <= n; i ++) {
w[0][i] = min(w[0][i], read());
a[i] = read(); long long k = read();
for(int j = 1; j <= k; j ++) {
long long x = read(), y = read();
w[x][i] = min(w[x][i], y);
}
}
long long res = INF;
for(int i = a[1] - m; i <= a[1]; i ++)
res = min(res, dijkstra(i, i + m));
cout << res << "\n";
return 0;
}
\(\text{poj-1125}\)
给定 \(n\) 个点的有向图,找到一个中心点使得到所有点的距离最大值最小。
\(1 \le n \le 100\)。
考虑到 \(n\) 比较小,直接枚举一下每个点到所有点的距离最大值取 \(\min\) 即可。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<long long, long long>
#define fi first
#define se second
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, dis[MAXN];
vector<pii > v[MAXN];
bool vis[MAXN];
void dijkstra(long long s) {
priority_queue<pii, vector<pii >, greater<pii > > q;
for(int i = 1; i <= n; i ++) dis[i] = INF, vis[i] = 0;
dis[s] = 0, q.push({0, s});
while(!q.empty()) {
long long x = q.top().se; q.pop();
if(vis[x]) continue; vis[x] = true;
for(auto p : v[x]) {
long long y = p.fi, w = p.se;
if(vis[y]) continue;
if(dis[y] > dis[x] + w)
dis[y] = dis[x] + w, q.push({dis[y], y});
}
}
return;
}
int main() {
while(n = read()) {
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= n; i ++) {
long long m = read();
for(int j = 1; j <= m; j ++) {
long long x = read(), w = read();
v[i].push_back({x, w});
}
}
long long ans = INF, mk = 0;
for(int i = 1; i <= n; i ++) {
dijkstra(i); long long res = 0;
for(int j = 1; j <= n; j ++) res = max(res, dis[j]);
if(res < ans) ans = res, mk = i;
}
cout << mk << " " << ans << "\n";
}
return 0;
}
\(\text{poj-2240}\)
套利是利用货币汇率之间的差异,将一种货币的一个单位转变为同一种货币的多个单位。
例如,假设 \(1\) 美元可以兑换 \(0.5\) 英镑,\(1\) 英镑可以兑换 \(10.0\) 法郎,而 \(1\) 法郎可以兑换 \(0.21\) 美元。那么,通过货币兑换,一个聪明的交易者可以从 \(1\) 美元开始,购买 \(0.5 \times 10.0 \times 0.21 = 1.05\) 美元,从而获得 \(5\%\) 的利润。
你的任务是编写一个程序,接受一组货币汇率作为输入,然后判断是否可能进行套利。
\(1 \le n \le 30\)。
实际上就是用 \(\text{spfa}\) 判负环。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
using namespace std;
#define MAXN 50
#define fi first
#define se second
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
vector<pair<long long, double> > v[MAXN];
map<string, long long> mp;
bool vis[MAXN], fg;
long long T, n, m;
double dis[MAXN];
void spfa(long long s) {
queue<long long> q;
for(int i = 1; i <= n; i ++) dis[i] = 0;
dis[s] = 100000, q.push(s);
while(!q.empty()) {
long long x = q.front(); q.pop();
for(auto p : v[x]) {
long long y = p.fi, w = p.se * dis[x];
if(dis[y] < w) {
if(s == y) { fg = 1; return; }
dis[y] = w, q.push(y);
}
}
}
return;
}
int main() {
while(n = read()) {
for(int i = 1; i <= n; i ++) {
string s; cin >> s;
mp[s] = i;
}
m = read(), fg = 0;
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= m; i ++) {
string s, t; double w; cin >> s >> w >> t;
v[mp[s]].push_back({mp[t], w});
}
for(int i = 1; i <= n; i ++) { spfa(i); if(fg) break; }
cout << "Case " << (++ T) << ": " << (fg ? "Yes" : "No") << "\n";
}
return 0;
}
\(\text{poj-2387}\)
给定 \(n\) 个点 \(m\) 条边的无向图,求 \(n \to 1\) 的最短路。
\(2 \le n \le 1000\),\(1 \le m \le 2000\)。
直接跑 \(\text{dijkstra}\) 即可,模板题。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 1005
#define pii pair<long long, long long>
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, dis[MAXN];
vector<pii > v[MAXN];
bool vis[MAXN];
void dijkstra(long long s) {
memset(dis, 0x3f, sizeof dis);
priority_queue<pii > q;
dis[s] = 0, q.push({0, s});
while(!q.empty()) {
long long x = q.top().second; q.pop();
if(vis[x]) continue; vis[x] = true;
for(auto p : v[x]) {
long long y = p.first, w = p.second;
if(!vis[y] && dis[y] > dis[x] + w)
dis[y] = dis[x] + w, q.push({-dis[y], y});
}
}
return;
}
int main() {
m = read(), n = read();
for(int i = 1; i <= m; i ++) {
long long x = read(), y = read(), w = read();
v[x].push_back({y, w});
v[y].push_back({x, w});
}
dijkstra(1);
cout << dis[n] << "\n";
return 0;
}
\(\text{luogu-1821}\)
寒假到了,\(n\) 头牛都要去参加一场在编号为 \(x\) 的牛的农场举行的派对,农场之间有 \(m\) 条有向路,每条路都有一定的长度。
每头牛参加完派对后都必须回家,无论是去参加派对还是回家,每头牛都会选择最短路径,求这 \(n\) 头牛的最短路径(一个来回)中最长的一条路径长度。
\(1 \le x \le n \le 1000\),\(1 \le m \le 10^5\),\(1 \le u, v \le n\),\(1 \le w \le 100\)。
建一个原图和一个反图即可,跑两遍最短路。
\(\text{hdu-2544}\)
给定 \(n\) 个点 \(m\) 条边的无向图,求 \(1 \to n\) 的最短路。
\(2 \le n \le 100\),\(1 \le m \le 10^4\)。
数据范围支持 \(O(n^3)\),于是用 \(\text{Floyd}\) 即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 105
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, f[MAXN][MAXN];
int main() {
while(n = read()) {
m = read(); memset(f, 0x3f, sizeof f);
for(int i = 1; i <= m; i ++) {
long long x = read(), y = read(), w = read();
f[x][y] = f[y][x] = w;
}
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
cout << f[1][n] << "\n";
}
return 0;
}
\(\text{hdu-1385}\)
给定 \(n\) 个城市,每个城市之间可能有一条道路或者没有。
经过一个城市需要收取 \(a_i\) 的过路费,经过 \(i \to j\) 的道路需要收取 \(f_{i,j}\) 的费用。
若干次询问,每次询问 \(s \to t\) 的最小费用和对应的路径,若有多条路径输出最短的那条。
输入格式比较猎奇,但是用 \(\text{read()}\) 读入的话感觉还是比较好处理的。
其次记录路径可以记录每次转移的前驱或者后继,具体看代码。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 105
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, f[MAXN][MAXN], nxt[MAXN][MAXN], a[MAXN], s, t;
int main() {
while(n = read()) {
memset(f, 0x3f, sizeof f);
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
f[i][j] = read(), nxt[i][j] = j;
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
if(f[i][j] == -1) f[i][j] = 0x3f3f3f3f3f3f3f3f;
for(int i = 1; i <= n; i ++) a[i] = read();
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) {
if(f[i][j] > f[i][k] + f[k][j] + a[k])
f[i][j] = f[i][k] + f[k][j] + a[k], nxt[i][j] = nxt[i][k];
if(f[i][j] == f[i][k] + f[k][j] + a[k])
nxt[i][j] = min(nxt[i][j], nxt[i][k]);
}
while((s = read()) && (t = read()) != -1) {
if(s == t) {
cout << "From " << s << " to " << s << " :\nPath: " << s;
cout << "\nTotal cost : 0\n\n";
continue;
}
cout << "From " << s << " to " << t << " :\nPath: " << s;
for(int i = nxt[s][t]; i != t; i = nxt[i][t]) cout << "-->" << i;
cout << "-->" << t << "\nTotal cost : " << f[s][t] << "\n\n";
}
}
return 0;
}
\(\text{hdu-1704}\)
给定 \(n\) 个人和 \(m\) 条胜负关系,关系具有传递性,求出有多少组人的胜负关系无法确定。
\(1 \le n \le 500\)。
传递闭包模板题,但是由于 \(n\) 较大,所以需要稍微剪枝一下。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 505
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long T, n, m, f[MAXN][MAXN];
int main() {
T = read();
while(T --) {
n = read(), m = read();
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) f[i][j] = 0;
for(int i = 1; i <= m; i ++) f[read()][read()] = 1;
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++)
for(int j = 1; f[i][k] && j <= n; j ++) f[i][j] |= f[i][k] & f[k][j];
long long ans = 0;
for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
if(!f[i][j] && !f[j][i]) ans ++;
cout << ans << "\n";
}
return 0;
}
\(\text{hdu-1599}\)
给定 \(n\) 个点和 \(m\) 条边的无向图,求图中的最小环长,环中至少包含 \(3\) 个点。
若没有环输出 It's impossible.。
\(1 \le n \le 100\),\(1 \le m \le 1000\)。
可以直接用 \(\text{Floyd}\) 求最小环长,要求至少包含 \(3\) 个点,稍微处理一下。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 110
#define INF 0x3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, f[MAXN][MAXN], mp[MAXN][MAXN];
int main() {
while(scanf("%lld%lld", &n, &m) != EOF) {
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) f[i][j] = mp[i][j] = INF;
for(int i = 1; i <= n; i ++) f[i][i] = mp[i][i] = 0;
for(int i = 1; i <= m; i ++) {
long long x, y, w; scanf("%lld%lld%lld", &x, &y, &w);
f[x][y] = f[y][x] = mp[x][y] = mp[y][x] = min(f[x][y], w);
}
long long ans = INF;
for(int k = 1; k <= n; k ++) {
for(int i = 1; i < k; i ++) for(int j = i + 1; j < k; j ++)
ans = min(ans, mp[i][k] + mp[k][j] + f[i][j]);
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
if(ans != INF) printf("%lld\n", ans);
else printf("It's impossible.\n");
}
return 0;
}
\(\text{hdu-3631}\)
给定 \(n\) 个点 \(m\) 条边的有向图,有重边,有自环。选中一些点,在这些点里面求两点的最短路。
有 \(q\) 次以下两种操作:
0 x表示选中 \(x\),若 \(x\) 之前已经被选中,输出ERROR! At point x。1 x y表示查询 \(x \to y\) 的最短路,若 \(x\) 或 \(y\) 未被选中,输出ERROR! At path x to y。若无法通过选中点到达,输出No such path;否则输出最短路径长度。
\(1 \le n \le 300\),\(1 \le m \le 10^4\),\(1 \le q \le 10^5\)。
每次选中 \(x\) 后以 \(x\) 为中转点跑一遍 \(\text{Floyd}\) 即可,最多跑 \(n\) 次,所以时间复杂度为 \(O(n^3)\)。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 305
#define INF 0x3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, m, q, cnt, f[MAXN][MAXN];
bool vis[MAXN];
void Floyd(long long k) {
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
return;
}
int main() {
while(n = read()) {
m = read(), q = read();
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) f[i][j] = INF;
for(int i = 1; i <= n; i ++) f[i][i] = vis[i] = 0;
for(int i = 1; i <= m; i ++) {
long long x = read(), y = read(), w = read();
f[x + 1][y + 1] = min(f[x + 1][y + 1], w);
}
cout << "Case " << (++ cnt) << ":\n";
while(q --) {
long long op = read(), x, y;
if(!op) {
x = read() + 1;
if(vis[x]) cout << "ERROR! At point " << x - 1 << "\n";
else vis[x] = 1, Floyd(x);
}
else {
x = read() + 1, y = read() + 1;
if(!vis[x] || !vis[y])
cout << "ERROR! At path " << x - 1 << " to " << y - 1 << "\n";
else if(f[x][y] == INF) cout << "No such path\n";
else cout << f[x][y] << "\n";
}
}
}
return 0;
}
\(\text{hdu-1596}\)
给定 \(n\) 个城市之间的旅行安全系数 \((0 \le x \le 1)\),定义两个城市之间的旅行安全系数为路径上的安全系数乘积,有 \(q\) 次询问,每次询问两个城市之间的旅行安全系数的最大值。
\(1 \le n \le 1000\)。
时限 \(5s\),考虑直接跑 \(\text{Floyd}\),把条件改一下就好了。
注意: \(\text{hdu}\) 貌似 \(\text{scanf()}\) 比 \(\text{cin}\) 快,用 \(\text{cin}\) 超时了。
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 1005
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, q, x, y;
double f[MAXN][MAXN];
int main() {
while(scanf("%lld", &n) != EOF) {
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) scanf("%lf", &f[i][j]);
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++)
if(f[i][k] != 0.0) for(int j = 1; j <= n; j ++)
f[i][j] = max(f[i][j], f[i][k] * f[k][j]);
scanf("%lld", &q);
while(q --) {
scanf("%lld %lld", &x, &y);
if(f[x][y] == 0.0) printf("What a pity!\n");
else printf("%.3lf\n", f[x][y]);
}
}
return 0;
}
\(\text{hdu-1869}\)
给定 \(n\) 个人及其认识关系,“六度分离”理论是指任意两个人中间最多隔 \(6\) 个人就可以联系在一起。
根据给定的认识关系,判断这 \(n\) 个人是否符合“六度分离”理论。
\(1 \le n < 100\),\(1 \le m < 200\)。
直接跑 \(\text{Floyd}\),若存在两个人之间距离大于 \(7\) 则不符合“六度分离”理论。
\(\text{hdu-3986}\)
给一个无向图,问任意删除一条边,求 \(1 \to n\) 的最短路径的最长长度。无法到达输出 \(-1\)。
\(2 \le n \le 1000\),\(3 \le m \le 5 \times 10^4\),\(1 \le w < 1000\)。
对所有边都跑一边 \(\text{dijkistra}\) 会超时,考虑到只有删除最短路径上的边会产生贡献。
于是只用枚举删除路径上的边就好了,第一次跑的时候记录一下前驱。
注意:无向图链式前向星数组开二倍!而且 \(\text{hdu}\) 会把 \(\text{RE}\) 显示成 \(\text{TLE}\)。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int MAXN = 100005;
const int INF = 0x3f3f3f3f;
int T, n, m, hd[MAXN], nxt[MAXN], tot, dis[MAXN], pre[MAXN];
struct node { int to, w; } e[MAXN];
priority_queue<pair<int, int> > q;
void add(int x, int y, int w) {
nxt[tot] = hd[x], hd[x] = tot, e[tot].to = y;
e[tot ++].w = w; return;
}
void dijkstra(int s, int fg) {
memset(dis, 0x3f, sizeof dis);
dis[s] = 0, q.push({0, s});
while(!q.empty()) {
int x = q.top().second, d = q.top().first;
q.pop(); if(d > dis[x]) continue;
for(int i = hd[x]; i != -1; i = nxt[i]) {
int y = e[i].to, w = e[i].w;
if(w == INF) continue;
if(dis[y] > dis[x] + w) {
dis[y] = dis[x] + w, q.push({-dis[y], y});
if(fg) pre[y] = i;
}
}
}
return;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T --) {
cin >> n >> m; tot = 0;
memset(pre, -1, sizeof pre);
memset(hd, -1, sizeof hd);
for(int i = 1; i <= m; i ++) {
int x, y, w; cin >> x >> y >> w;
add(x, y, w), add(y, x, w);
}
dijkstra(1, 1);
if(dis[n] == INF) { cout << "-1\n"; continue; }
int ans = 0;
for(int x = n; x != 1; x = e[pre[x] ^ 1].to) {
int s = pre[x], t = s ^ 1, mk = e[s].w;
e[s].w = e[t].w = INF;
dijkstra(1, 0);
ans = max(ans, dis[n]);
if(ans == INF) break;
e[s].w = e[t].w = mk;
}
if(ans == INF) cout << "-1\n";
else cout << ans << "\n";
}
return 0;
}
\(\text{hdu-3832}\)
有 \(n\) 盏路灯,分别位于 \((x_i, y_i)\) 且覆盖半径为 \(r_i\)。
若两盏路灯照射范围有相交(点相交也算),则认为两盏路灯是连接的。
求最多关闭多少盏路灯使得第 \(1,2,3\) 盏路灯仍联通。
\(3 \le n \le 200\),\(1 \le x_i, y_i, r_i \le 1000\)。
这种问题貌似被称为“斯坦纳树问题”。
不过不重要,可以发现使得这三个点联通则一定会有一个点连接这三个点。
于是考虑枚举这个点,取 \(\min\) 即可。
注意:不要初始化 \(dis_i\) 为极大值,因为取 \(\min\) 的时候累加会爆 \(\text{long long}\)!!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
#define MAXN 205
#define INF 0x3f3f3f3f
struct node { long long x, y, r; } e[MAXN];
long long T, n, dis[5][MAXN];
vector<long long> v[MAXN];
bool check(node a, node b) {
long long dis = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
return (dis <= (a.r + b.r) * (a.r + b.r));
}
void dijkstra(long long st) {
for(int i = 1; i <= n; i ++) dis[st][i] = INF;
priority_queue<pair<long long, long long> > q;
dis[st][st] = 0, q.push({0, st});
while(!q.empty()) {
long long x = q.top().second, d = q.top().first; q.pop();
if(-d > dis[st][x]) continue;
for(int i = 0; i < v[x].size(); i ++) {
long long y = v[x][i];
if(dis[st][y] > dis[st][x] + 1)
dis[st][y] = dis[st][x] + 1, q.push({-dis[st][y], y});
}
}
return;
}
int main() {
scanf("%lld", &T);
while(T --) {
scanf("%lld", &n);
for(int i = 1; i <= n; i ++) scanf("%lld%lld%lld", &e[i].x, &e[i].y, &e[i].r);
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= n; i ++) for(int j = i + 1; j <= n; j ++)
if(check(e[i], e[j])) v[i].push_back(j), v[j].push_back(i);
dijkstra(1), dijkstra(2), dijkstra(3);
long long ans = INF;
for(int i = 1; i <= n; i ++)
ans = min(ans, dis[1][i] + dis[2][i] + dis[3][i]);
if(ans == INF) printf("-1\n");
else printf("%lld\n", n - ans - 1);
}
return 0;
}
\(\text{poj-3463}\)
给定 \(n\) 个点 \(m\) 条边的有向图,求 \(s \to t\) 的最短路和比最短路恰好多一单位的次短路的路径数。
\(2 \le n \le 1000\),\(1 \le m \le 10^4\),\(1 \le w_i \le 1000\)。
其实就是最短路和次短路计数,这个恰好多一单位实际上不太影响。
考虑在 \(\text{dijkstra}\) 过程中记录,每次松弛的时候判断一下要更新什么。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define MAXN 1005
#define pii pair<long long, long long>
#define INF 0x3f3f3f3f3f3f3f3f
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long T, n, m, dis[MAXN][2], cnt[MAXN][2];
struct node { long long x, dis, p; };
priority_queue<node> q;
vector<pii > v[MAXN];
bool vis[MAXN][2];
bool operator < (const node l, const node r) {
return l.dis > r.dis;
}
void dijkstra(long long s) {
memset(vis, 0, sizeof vis);
memset(cnt, 0, sizeof cnt);
memset(dis, 0x3f, sizeof dis);
dis[s][0] = 0, cnt[s][0] = 1;
q.push({s, 0, 0});
while(!q.empty()) {
long long x = q.top().x, p = q.top().p;
q.pop(); if(vis[x][p]) continue;
vis[x][p] = true;
for(auto t : v[x]) {
long long y = t.first, d = dis[x][p] + t.second;
if(d < dis[y][0]) {
if(dis[y][0] != INF)
dis[y][1] = dis[y][0], cnt[y][1] = cnt[y][0],
q.push({y, dis[y][0], 1});
dis[y][0] = d, cnt[y][0] = cnt[x][p];
q.push({y, d, 0});
}
else if(d == dis[y][0]) cnt[y][0] += cnt[x][p];
else if(d == dis[y][1]) cnt[y][1] += cnt[x][p];
else if(d < dis[y][1])
dis[y][1] = d, cnt[y][1] = cnt[x][p], q.push({y, d, 1});
}
}
return;
}
int main() {
cin >> T;
while(T --) {
cin >> n >> m;
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= m; i ++) {
long long x, y, w; cin >> x >> y >> w;
v[x].push_back({y, w});
}
long long s, t; cin >> s >> t;
dijkstra(s); long long ans = cnt[t][0];
if(dis[t][0] == dis[t][1] - 1) ans += cnt[t][1];
cout << ans << "\n";
}
return 0;
}
\(\text{hdu-4370}\)
给定一个 \(n \times n\) 的矩阵 \(A\),你需要构造一个 \(n \times n\) 的 \(01\) 矩阵 \(B\),满足:
- \(b_{1,2}+b_{1,3}+\dots +b_{1,n}=1\)。
- \(b_{1,n}+b_{2,n}+\dots +b_{n-1,n}=1\)。
- 对于 \(i \in [2,n-1]\),满足 \(\sum\limits_{k=1}^{n} b_{k,i} = \sum\limits_{j=1}^{n} b_{i,j}\)。
在满足条件的前提下,求 \(\sum\limits_i \sum\limits_j a_{i,j} \times b_{i,j}\) 的最小值。
\(2 \le n \le 300\),\(1 \le a_{i,j} \le 10^5\)。
其实可以把这个 \(A\) 矩阵当成邻接矩阵建图,单向边,边权为 \(a_{i,j}\)。
那么条件就可以转化为,\(1\) 只有一个出度,\(n\) 只有一个入度,\(2 \sim n-1\) 入度等于出度。
于是只有两种情况满足条件,要么一条 \(1 \to n\) 的链;要么一个 \(1 \to 1\) 的环和一个 \(n \to n\) 的环。
但是需要保证不能是 \(1,n\) 的自环,因为不满足原条件 \(1,2\)。
跑最短路就好了,答案取 \(\min\)。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 305
#define pii pair<long long, long long>
#define fi first
#define se second
long long read() {
long long x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
long long n, a[MAXN][MAXN], dis[MAXN], ans;
vector<pii > v[MAXN];
bool vis[MAXN];
long long spfa(long long s) {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
queue<long long> q; q.push(s);
dis[s] = 0, vis[s] = 1;
long long res = 0x3f3f3f3f;
while(!q.empty()) {
long long x = q.front(); q.pop();
vis[x] = 0;
for(auto it : v[x]) {
long long y = it.fi, w = it.se;
if(dis[y] > dis[x] + w) {
dis[y] = dis[x] + w;
if(!vis[y]) vis[y] = 1, q.push(y);
}
if(y == s) res = min(res, dis[x] + w);
}
}
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
while(cin >> n) {
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) cin >> a[i][j];
for(int i = 1; i <= n; i ++) v[i].clear();
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
if(i != j) v[i].push_back({j, a[i][j]});
long long x = spfa(1); ans = dis[n];
cout << min(ans, spfa(n) + x) << "\n";
}
return 0;
}
\(\text{luogu-6961}\)
给定一张 \(n\) 个点 \(m\) 条边的无向图,求 \(1 \to n\) 路径上前 \(k\) 大边权的和。
\(2 \le n \le 3000\),\(1 \le m \le 3000\),\(1 \le k < n\),\(1 \le w_i \le 10^9\)。
下面部分题解来自于 题解:P6961 NEERC 2017 Journey from Petersburg to Moscow - 洛谷专栏。
我们假设已经知道第 \(k\) 大的边权为 \(w\),则我们可以把边权大于等于 \(w\) 的边的边权 \(x\) 全部变成 \(x-w\),小于 \(w\) 的边权全部变成 \(0\),再跑最短路。答案就是 \(d_n + k \times w\)。其中 \(d_n\) 表示 \(1 \to n\) 的在新图上的最短路长度。
但是现在我们不知道真正的第 \(k\) 大的边权是多少,我们考虑稀里糊涂的枚举每一条边,把它当作第 \(k\) 大的边权再来跑,得到的结果对答案的影响。
下面是证明。为了方便证明,我们先符号化一下。
按题目中的符号 \(1 \to n\) 的一条路径上有 \(l\) 条边,依次记为 \(c_1,c_2,\cdots,c_l\),不妨令 \(c_1 \ge c_2 \ge \cdots \ge c_l\)。
真实的答案 \(A= \sum \limits_{i=1} ^k c_i = \sum \limits _{i=1 } ^l \max (c_i -c_k ,0) + k \times c_k\),我们钦定第 \(t\) 条边为第 \(k\) 大的边后得到的答案 \(A_t = \sum \limits _{i=1 } ^l \max (c_i -c_t ,0) + k \times c_t\)。
- \(c_t = c_k\),显然 \(A=A_t\)。
- \(c_t < c_k \Rightarrow t>k\),即我们钦定的这条边更小。
由 \(i \le t \Rightarrow c_i \ge c_t\),故 \(A \le A_t\)。
- \(c_t > c_k \Rightarrow t < k\),即我们钦定的边更大。
由 \(i > t \Rightarrow c_i \le c_t\),故 \(A \le A_t\)。
综上所述,\(\forall 1 \le t \le l,A \le A_t\),且 \(\exists 1 \le t \le l,A=A_t\)。该结论对于每一条路径都成立,而我们又是在求最短路,所以答案一定能取到。证毕!
用 dijkstra 实现最短路,复杂度为 \(\Theta(m^2 \log n)\)。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define ll long long
#define MAXN 3005
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<ll, ll>
#define fi first
#define se second
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c < 48 || c > 57) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 3) + (x << 1) + (c - 48); c = getchar(); }
return x * f;
}
ll n, m, k, res = INF, dis[MAXN], vis[MAXN], w[MAXN];
vector<pii > v[MAXN];
ll dijkstra(ll t) {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis);
priority_queue<pii > q;
dis[1] = 0, q.push({0, 1});
while(!q.empty()) {
ll x = q.top().se; q.pop();
if(vis[x]) continue; vis[x] = 1;
for(auto it : v[x]) {
ll y = it.fi, z = max(0ll, it.se - t);
if(dis[y] > dis[x] + z) dis[y] = dis[x] + z, q.push({-dis[y], y});
}
}
return dis[n] + k * t;
}
int main() {
n = read(), m = read(), k = read();
for(int i = 1; i <= m; i ++) {
ll x = read(), y = read(), z = read(); w[i] = z;
v[x].push_back({y, z}), v[y].push_back({x, z});
}
res = min(res, dijkstra(0));
for(int i = 1; i <= m; i ++) res = min(res, dijkstra(w[i]));
cout << res << "\n";
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19376193

浙公网安备 33010602011771号