最短路

\(\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\)

  1. \(c_t = c_k\),显然 \(A=A_t\)
  2. \(c_t < c_k \Rightarrow t>k\),即我们钦定的这条边更小。

\[\begin{align*} A - A_t &= \sum _{i=1} ^ l \big [ \max(c_i-c_k,0) -\max(c_i- c_t,0) \big ] + k \times ( c_k-c_t)\\ &= \sum _{i=1} ^k [(c_i - c_k) - (c_i - c_t)] + \sum _{i=k+1} ^t [0 - (c_i - c_t)] + \sum _{i=t+1} ^l [0-0] + k \times ( c_k-c_t)\\ &=k \times ( c_t-c_k) + \sum _{i=k+1} ^t (c_t-c_i) + k \times ( c_k-c_t) \\ &= \sum _{i=k+1} ^t (c_t-c_i) \end{align*} \]

\(i \le t \Rightarrow c_i \ge c_t\),故 \(A \le A_t\)

  1. \(c_t > c_k \Rightarrow t < k\),即我们钦定的边更大。

\[\begin{align*} A - A_t &= \sum _{i=1} ^ l \big [ \max(c_i-c_k,0) -\max(c_i- c_t,0) \big ] + k \times ( c_k-c_t)\\ &=\sum _{i=1 } ^t [(c_i - c_k) - (c_i - c_t)] + \sum _{t+1} ^ k [(c_i - c_k) - 0] + \sum _{i=k+1} ^ l [0-0] + k \times ( c_k-c_t)\\ &= t \times (c_t- c_k ) + \sum _{t+1} ^ k (c_i - c_k) + k \times ( c_k-c_t) \\ &= \sum _{i=t+1} ^ k (c_i - c_k +c_k-c_t) \\ &= \sum _{i=t+1} ^ k (c_i -c_t) \end{align*} \]

\(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;
}
posted @ 2025-12-20 15:53  So_noSlack  阅读(4)  评论(0)    收藏  举报