はじめに
これは、「情報科学オリンピックワンパス・改善」という本の演習の筆記の記録と学習ノートを記録するために使用されます。
これは通常、特別なトピックで書かれています(すべてをまとめてロードするには時間がかかる場合があります...)。たとえば、この章は最短パスを記録するために使用されます。
もう1つの文を挿入しましょう:Lojは本当に良いOJです。Luoguが最高のOIerコミュニティである場合、Lojが最高の質問領域です。
PS:ここでの「最高の」とは、良いスタイルの絵画など、私に最高の気持ちをもたらす演習のみを指し、個人的な意見のみを表しています。
特別なプレゼンテーション:最短ルート、グラフ理論の中心的なコンテンツ、多くのアルゴリズムを柔軟に使用する必要があり、さまざまなトピックは常にコアアイデアと切り離せません。
Dijksral、SPFA、Floydのいずれかを知らない場合は、私の記事をチェックしてください。
最初の質問
私は弱すぎるのですか?長年研究してきたフロイドは、この機能があることすら知らなかった……。
無向グラフの最小リングを見つけるには、フロイドが推奨されます。
ここでは主にフロイドを使用して解決する特定の原則について話します
フロイドを使用して無向グラフの最小サイクル問題を解決するには、まずフロイドの各ステップの重要性を理解する必要があります。
4層のコードは非常に優れていますが
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]);
しかし、このプロセスで何が起こったのか、なぜそれを使用して最小のループを見つけることができるのでしょうか?
まず真実を理解する:
エッジの1つを任意に削除する場合は、グラフの最小リング\(u \からv \へk \からu \)を考慮します\(u \からv \)、
次に、残りの\(v \からk \からu \)は、図の\(u \からv \)間の最短パスである必要があります。
では、これはフロイドのアルゴリズムとどのように関連しているのでしょうか。別の理由があります:
フロイドアルゴリズムが\(k_i \)を列挙すると、最初の\(k-1 \)ポイントの最短経路が取得されます。
これらの\(k-1 \)ポイントにはポイント\(k \)は含まれず、最短パスにも\(k \)ポイントは含まれません。
そして、私たちすることができますフロント\(K-1 \) 2点のいずれかのポイント\(I、J \) 、ので\は、(i \ jへ\) $ I、Jの$、とこの間の最短経路が既にありますパスには\(k \)ポイントが含まれていません。
接続だから\(IへのKへのI \ J \ \ \) 、我々はスルーを得る\(I、J、K \ ) 、最小リングの
最後に、\(ans \)の最小値を毎回更新します。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cmath>
#include <vector>
#define N 310
#define maxd 0x3f3f3f3f
using namespace std;
int n, m, a[N][N], d[N][N], pre[N][N];
long long ans = maxd;
vector<int> path;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void get_path(int x, int y) {
if (!pre[x][y])
return;
get_path(x, pre[x][y]);
path.push_back(pre[x][y]);
get_path(pre[x][y], y);
return;
}
int main() {
n = read();
m = read();
int x, y, z;
memset(a, 0x3f, sizeof(a));
for (int i = 1; i <= n; i++) a[i][i] = 0;
for (int i = 1; i <= m; i++) {
x = read();
y = read();
z = read();
a[x][y] = a[y][x] = min(a[x][y], z);
}
memcpy(d, a, sizeof(a));
for (int k = 1; k <= n; k++) {
for (int i = 1; i < k; i++)
for (int j = i + 1; j < k; j++)
if ((long long)d[i][j] + a[j][k] + a[k][i] < ans) {
ans = d[i][j] + a[j][k] + a[k][i];
path.clear();
path.push_back(i);
get_path(i, j);
path.push_back(j);
path.push_back(k);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
pre[i][j] = k;
}
}
if (ans == maxd) {
printf("No solution.\n");
return 0;
}
for (int i = 0; i < path.size(); i++) printf("%d ", path[i]);
return 0;
}
第二の質問
この方法を使用して、この問題を解決しました。この方法では、最短パスよりもスペースが節約され、以下のコードも私の方法です。
しかし、これは、ここで階層化グラフの最短経路を説明するという考えには影響しません。
より直接的な考え方は、グラフを\(2 ^ k \)レイヤーに分割することです(\(k \)はキーの数です)。各レイヤーはキーの状態を示します。(圧力だけではない)
そして、どこでもファックさえ側に、非常に複雑なグラフィックスを取得し、あなたが最もショートの上で実行することができます。
注:答えは\(max_ {1 \ leq i \ leq 2 ^ k} \ {dis [i] [n] \} \)です。以下は、BFS +圧力のコードです。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <cstring>
#include <queue>
#define N 15
using namespace std;
int n, m, p, k, s;
int door[N][N][N][N], key[N][N][N], dis[N][N][1 << 14], cnt[N][N];
bool vis[N][N][1 << 14];
struct node {
int x, y, val;
};
int dx[5] = { 0, 0, 0, 1, -1 };
int dy[5] = { 0, 1, -1, 0, 0 };
queue<node> q;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
bool chck(int x, int y, int fx, int fy, int val, int nxt) {
if (fx < 1 || fx > n || fy < 1 || fy > m)
return false;
int d = door[x][y][fx][fy];
if (d == -1)
return false;
if (d && !(val & (1 << d)))
return false;
if (vis[fx][fy][nxt])
return false;
return true;
}
int bfs() {
memset(vis, false, sizeof(vis));
int first = 0;
for (int i = 1; i <= cnt[1][1]; i++) first |= (1 << key[1][1][i]);
q.push((node){ 1, 1, first });
while (!q.empty()) {
int x = q.front().x;
int y = q.front().y;
int val = q.front().val;
q.pop();
if (x == n && y == m)
return dis[x][y][val];
for (int i = 1; i <= 4; i++) {
int fx = dx[i] + x;
int fy = dy[i] + y;
int nxt = val;
for (int i = 1; i <= cnt[fx][fy]; i++) nxt |= (1 << key[fx][fy][i]);
if (!chck(x, y, fx, fy, val, nxt))
continue;
vis[fx][fy][nxt] = true;
dis[fx][fy][nxt] = dis[x][y][val] + 1;
q.push((node){ fx, fy, nxt });
}
}
return -1;
}
int main() {
n = read();
m = read();
p = read();
k = read();
int type, x, y, xx, yy;
for (int i = 1; i <= k; i++) {
x = read();
y = read();
xx = read();
yy = read();
type = read();
if (type)
door[x][y][xx][yy] = door[xx][yy][x][y] = type;
else
door[x][y][xx][yy] = door[xx][yy][x][y] = -1;
}
s = read();
for (int i = 1; i <= s; i++) {
x = read();
y = read();
type = read();
key[x][y][++cnt[x][y]] = type;
}
printf("%d\n", bfs());
return 0;
}
三番目の質問
4番目の質問
私は前の試験で元の質問を受けました...そして、それが一方向のエッジであることに気づかなかったので、直接\(max \ {dis [i] \ times 2 \} \)を出力し、次に0をバーストして厚くしました....
それから試験の後、私は非常に暴力的な方法を考えました:
これは、各ノードをルートノードとして使用して単一ソースの最短パスを実行することであり、答えは\(max \ {dis [1、i] + dis [i、1] \} \)です。
とても速く走るようです
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#define N 1010
#define M 100010
using namespace std;
int n, m, v, dis[N], diss[N], head[N], cnt = 0;
bool vis[N];
struct Edge {
int nxt, to, val;
} edge[M];
priority_queue<pair<int, int> > q;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void addedge(int x, int y, int z) {
cnt++;
edge[cnt].nxt = head[x];
edge[cnt].to = y;
edge[cnt].val = z;
head[x] = cnt;
return;
}
void dij(int s) {
memset(vis, false, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
q.push(make_pair(0, s));
while (!q.empty()) {
int now = q.top().second;
q.pop();
if (vis[now])
continue;
vis[now] = true;
for (int i = head[now]; i; i = edge[i].nxt) {
int y = edge[i].to, z = edge[i].val;
if (dis[y] > dis[now] + z) {
dis[y] = dis[now] + z;
q.push(make_pair(-dis[y], y));
}
}
}
return;
}
int main() {
n = read(), m = read(), v = read();
int x, y, z;
for (int i = 1; i <= m; i++) {
x = read(), y = read(), z = read();
addedge(x, y, z);
}
dij(v);
memcpy(diss, dis, sizeof(dis));
int ans = 0;
for (int i = 1; i <= n; i++) {
dij(i);
ans = max(ans, dis[v] + diss[i]);
}
printf("%d\n", ans);
return 0;
}
もちろん、これは前向きな解決策ではありません。(ナンセンス)
つまり、両側で最短パスを2回実行するだけで十分です。(ここでは単純なSPFAで実装されています)
#include <bits/stdc++.h>
using namespace std;
int n, m, k, ans;
int ecnt = 0, tcnt = 0;
int zhead[1000 + 5], fhead[1000 + 5];
int zdis[1000 + 5], fdis[1000 + 5];
bool vst[1000 + 5];
struct edge {
int from, to, len;
int nxt;
} g[100000 + 5], f[100000 + 5];
inline int read() {
int x = 0;
char ch = getchar();
while (ch < 48 || ch > 57) ch = getchar();
while (ch <= 57 && ch >= 48) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x;
}
inline void addedge1(int u, int v, int w) {
ecnt++;
g[ecnt].from = u;
g[ecnt].to = v;
g[ecnt].len = w;
g[ecnt].nxt = zhead[u];
zhead[u] = ecnt;
}
inline void addedge2(int u, int v, int w) {
tcnt++;
f[ecnt].from = u;
f[ecnt].to = v;
f[ecnt].len = w;
f[ecnt].nxt = fhead[u];
fhead[u] = tcnt;
}
inline void SPFA1(int S) {
memset(fdis, 0x3f, sizeof(fdis));
memset(vst, 0, sizeof(vst));
vst[S] = 1;
queue<int> q;
fdis[S] = 0;
q.push(S);
while (!q.empty()) {
int u = q.front();
q.pop();
vst[u] = 0;
for (register int i = fhead[u]; i; i = f[i].nxt) {
int v = f[i].to;
if (fdis[v] > fdis[u] + f[i].len) {
fdis[v] = fdis[u] + f[i].len;
if (!vst[v]) {
vst[v] = 1;
q.push(v);
}
}
}
}
}
inline void SPFA2(int S) {
memset(zdis, 0x3f, sizeof(zdis));
memset(vst, 0, sizeof(vst));
vst[S] = 1;
queue<int> q;
zdis[S] = 0;
q.push(S);
while (!q.empty()) {
int u = q.front();
q.pop();
vst[u] = 0;
for (register int i = zhead[u]; i; i = g[i].nxt) {
int v = g[i].to;
if (zdis[v] > zdis[u] + g[i].len) {
zdis[v] = zdis[u] + g[i].len;
if (!vst[v]) {
vst[v] = 1;
q.push(v);
}
}
}
}
}
int main() {
n = read(), m = read(), k = read();
for (register int i = 1, x, y, z; i <= m; i++) {
x = read(), y = read(), z = read();
addedge1(x, y, z);
addedge2(y, x, z);
}
SPFA1(k);
SPFA2(k);
for (register int i = 1; i <= n; i++) ans = max(ans, zdis[i] + fdis[i]);
printf("%d\n", ans);
system("pause");
return 0;
}
問題5
厳密な二次短絡を求める方法は3つあります。
- Dijksralは再び2つの値を記録します。
- Dijkscal + A *再び。
- ダイクスカル2回。
この記事の内容は以下のとおりです。
ダイクストラ:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cmath>
#include <queue>
#define N 5010
#define M 100010
using namespace std;
int n, m, dis[N], diss[N], head[N], cnt = 0;
struct Edge {
int nxt, to, val;
} ed[M << 1];
priority_queue<pair<int, int> > q;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void addedge(int x, int y, int z) {
++cnt;
ed[cnt].nxt = head[x];
ed[cnt].to = y;
ed[cnt].val = z;
head[x] = cnt;
return;
}
void dij() {
memset(dis, 0x3f, sizeof(dis));
memset(diss, 0x3f, sizeof(diss));
dis[1] = 0;
q.push(make_pair(0, 1));
while (!q.empty()) {
int d = -q.top().first, now = q.top().second;
q.pop();
if (d > diss[now])
continue;
for (int i = head[now]; i; i = ed[i].nxt) {
int y = ed[i].to, z = ed[i].val;
int d2 = d + z;
if (dis[y] > d2) {
swap(dis[y], d2);
q.push(make_pair(-dis[y], y));
}
if (diss[y] > d2 && dis[y] < d2) {
diss[y] = d2;
q.push(make_pair(-diss[y], y));
}
}
}
return;
}
int main() {
n = read(), m = read();
int x, y, z;
for (int i = 1; i <= m; i++) {
x = read(), y = read(), z = read();
addedge(x, y, z);
addedge(y, x, z);
}
dij();
printf("%d\n", diss[n]);
return 0;
}
dijkstra + A *:
最初にdijkstraを使用してnから各点までの最短距離を処理し、次にA *アルゴリズムを呼び出します。
1から始めて、隣接するポイントのエッジの重みを毎回追加してから、それらをキューに格納します。これにより、1から各ポイントまでの距離を格納するたびに。
次に、この距離+現在のポイントからnまでの最短距離に従ってソートします。
次に、最初にn個のポイントが取り出されるときに、1からnまでの最短パスが取得され、その後、隣接するエッジの重みがキューに追加され続けます。
次に、nへの2番目の処理は、最短パスと、最短パスが通過する側(2番目に最短の距離)を除く最短側の値を足したものです。
#include <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=5000+10;
int n,m,u,v,val;
int dis[maxn];
struct P
{
int to,cost;
bool operator < (const P & a) const
{
return cost>a.cost;
}
};
vector<P> edge[maxn];
void Dijkstra()
{
fill(dis,dis+n+2,INF);
dis[n]=0;
priority_queue<P> qu;
qu.push(P{n,0});
while(!qu.empty())
{
P x=qu.top();
qu.pop();
for(int i=0;i<edge[x.to].size();i++)
{
P y=edge[x.to][i];
if(dis[y.to]>dis[x.to]+y.cost)
{
dis[y.to]=dis[x.to]+y.cost;
qu.push(P{y.to,dis[y.to]});
}
}
}
}
struct node
{
int to,len;
bool operator < (const node & a) const
{
return len+dis[to]>a.len+dis[a.to];
}
};
int A_star()
{//if(dis[1]==INF) return -1;当不存在最短路时要加上,不然会死循环。
priority_queue<node> qu;
qu.push(node{1,0});
int num=0;
while(!qu.empty())//这个地方有点搜索的味道,从1开始不断加上相邻的点的边权,然后放入队列。就是从1开始不断向n拓展
{
node a=qu.top();
qu.pop();
if(a.to==n) num++;
if(num==2) return a.len;
for(int i=0;i<edge[a.to].size();i++)
{
P b=edge[a.to][i];
qu.push(node{b.to,a.len+b.cost});//到b.to这个点,然后1到b.to的距离就是a.to的距离加上ab间边的权值。
}
}
return -1;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=0;i<=n;i++)
edge[i].clear();
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&val);
edge[u].push_back(P{v,val});
edge[v].push_back(P{u,val});
}
Dijkstra();
printf("%d\n",A_star());
}
return 0;
}
最短の2倍:
始点から最短パスを1回実行し、次に終点から最短パスを再度実行し、すべてのエッジをトラバースします。
2次短絡の距離は、1つのエッジの重みと、開始点から1点までの最短経路に、終点から別の点までの最短経路を加えたものです。
#include <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#pragma GCC optimize(2)
#define INF 0x3f3f3f3f
using namespace std;
const int N=5000+5;
int n,m,u,v,val;
struct P
{
int to,cost;
};
vector<vector<P> > G;//刚学的二维vector使用,使用前需要resize其大小
int dis1[N],dis2[N],vis[N];
void spfa(int s,int *dis)
{
memset(vis,0,sizeof(vis));
dis[s]=0;
queue<int> qu;
qu.push(s);
while(!qu.empty()){
int U=qu.front();
qu.pop();
vis[U]=0;
for(int i=0;i<G[U].size();i++){
P V=G[U][i];
if(dis[V.to]>dis[U]+V.cost){
dis[V.to]=dis[U]+V.cost;
if(!vis[V.to]){
vis[V.to]=1;
qu.push(V.to);
}
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF){
G.resize(n+1); G.clear();
memset(dis1,INF,sizeof(dis1));
memset(dis2,INF,sizeof(dis2));
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&val);
G[u].push_back(P{v,val});
G[v].push_back(P{u,val});
}
spfa(1,dis1);
spfa(n,dis2);
int ans=INF;
for(int i=1;i<=n;i++){//遍历所有的边
for(int j=0;j<G[i].size();j++){
P now=G[i][j];
int temp=dis1[i]+dis2[now.to]+now.cost;
if(temp>dis1[n] && temp<ans) ans=temp;//严格最小
}
}
printf("%d\n",ans);
}
return 0;
}
質問6
無向グラフを作成する権利はありませんが、答えを直接記録してもよいので、とても簡単です。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <vector>
#include <queue>
#define MOD 100003
#define N 1000010
using namespace std;
int n, m, dep[N], ans[N];
bool vis[N];
vector<int> v[N];
queue<int> q;
int main() {
scanf("%d %d", &n, &m);
int x, y;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
memset(vis, false, sizeof(vis));
q.push(1);
vis[1] = true;
ans[1] = 1;
while (!q.empty()) {
int now = q.front();
q.pop();
for (int i = 0; i < v[now].size(); i++) {
int to = v[now][i];
if (!vis[to]) {
vis[to] = true;
dep[to] = dep[now] + 1;
q.push(to);
}
if (dep[to] == dep[now] + 1)
ans[to] = (ans[to] + ans[now]) % MOD;
}
}
for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
// system("pause");
return 0;
}
質問7
#10078「Yi Bentong 3.2練習4」明けましておめでとう
ソースポイントとa,b,c,d,e
5つのノードからそれぞれ最短ルートを実行し、DFSブルートフォース列挙方式の数を記録します。(合計\(2 ^ 5 \))
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#define N 50010
#define M 100010
using namespace std;
int n, m, v, dis[10][N], head[N], cnt = 0, a[10], ans = 999999999;
bool vis[N];
struct Edge {
int nxt, to, val;
} edge[M << 1];
priority_queue<pair<int, int> > q;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void addedge(int x, int y, int z) {
cnt++;
edge[cnt].nxt = head[x];
edge[cnt].to = y;
edge[cnt].val = z;
head[x] = cnt;
return;
}
void dij(int x, int s) {
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) dis[x][i] = 0x3f3f3f3f;
dis[x][s] = 0;
q.push(make_pair(0, s));
while (!q.empty()) {
int now = q.top().second;
q.pop();
if (vis[now])
continue;
vis[now] = true;
for (int i = head[now]; i; i = edge[i].nxt) {
int y = edge[i].to, z = edge[i].val;
if (dis[x][y] > dis[x][now] + z) {
dis[x][y] = dis[x][now] + z;
q.push(make_pair(-dis[x][y], y));
}
}
}
return;
}
int path[10], use[N];
void work() {
int sum = dis[0][a[path[1]]];
for (int i = 2; i <= 5; i++) sum += dis[path[i - 1]][a[path[i]]];
ans = min(ans, sum);
return;
}
void dfs(int x) {
if (x == 6) {
work();
return;
}
for (int i = 1; i <= 5; i++) {
if (use[i])
continue;
use[i] = true;
path[x] = i;
dfs(x + 1);
use[i] = false;
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= 5; i++) a[i] = read();
int x, y, z;
for (int i = 1; i <= m; i++) {
x = read(), y = read(), z = read();
addedge(x, y, z);
addedge(y, x, z);
}
dij(0, 1);
for (int i = 1; i <= 5; i++) dij(i, a[i]);
memset(use, false, sizeof(use));
dfs(1);
printf("%d\n", ans);
return 0;
}
問題8
#10079「One Book 3.2 Exercise 5」Best Trade
古典的な階層図は最短です。
3層マップを作成します。
- 通常のレイヤーは、入力データに従ってグラフを保存することです。
- 購入層は、それを通常の層の対応する頂点に接続することであり、エッジの重みは購入価格の反対です。
- 販売レイヤーは、通常レイヤーの対応する頂点に接続するもので、エッジウェイトは販売価格です。
その後、私は負の力を持っているので、SPFAを再度実行できます。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <vector>
#define maxn 100010
#define maxm 500010
#define maxd 999999999
using namespace std;
int n, m, a[maxn], dis[maxn * 3 + 1];
bool use[maxn * 3 + 1];
queue<int> q;
struct node {
int to, val;
};
vector<node> v[maxn * 3 + 1];
void addedge(int x, int y) {
v[x].push_back((node){ y, 0 });
v[x + n].push_back((node){ y + n, 0 });
v[x + 2 * n].push_back((node){ y + 2 * n, 0 });
v[x].push_back((node){ y + n, -a[x] });
v[x + n].push_back((node){ y + 2 * n, a[x] });
return; //核心代码
}
void spfa() {
for (int i = 1; i <= n; i++) dis[i] = -maxd;
memset(use, false, sizeof(use));
q.push(1);
use[1] = true;
dis[1] = 0;
while (!q.empty()) {
int now = q.front();
q.pop();
use[now] = false;
for (int i = 0; i < v[now].size(); i++) {
int y = v[now][i].to;
int z = v[now][i].val;
if (dis[y] < dis[now] + z) {
dis[y] = dis[now] + z;
if (!use[y]) {
q.push(y);
use[y] = true;
}
}
}
}
return;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
int x, y, z;
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &x, &y, &z);
addedge(x, y);
if (z == 2)
addedge(y, x);
}
v[n].push_back((node){ n * 3 + 1, 0 });
v[n * 3].push_back((node){ n * 3 + 1, 0 });
n = 3 * n + 1;
spfa();
printf("%d\n", dis[n]);
// system("pause");
return 0;
}
問題9
#10080 "Yi Bentong 3.2演習6"車の給油
これは、古典的な階層グラフの最も短い問題でもあります。
アイデアは、グラフを\(k + 1 \)レイヤーに分割することです。各レイヤー間のノードエッジの重みは\(1 \)であり、レイヤー\(i \)は\(k-i + 1 \)も実行できることを示しますステップ。
ある場所にガソリンスタンドがある場合、高レベルからベースレベルまでのエッジの重みは\(A_i \)です。それ以外の場合は\(A_i + C_i \)です。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#define N 500010
#define M 5000100
#define maxd 999999999
using namespace std;
int n, k, a, b, c;
int dis[N], oil;
bool use[N];
int head[N], cnt = 0;
struct node {
int next, to, val;
} edge[M];
priority_queue<pair<int, int> > q;
void addedge(int x, int y, int z, int xx, int yy, int zz, int w) {
int p1 = (z - 1) * n * n + (x - 1) * n + y;
int p2 = (zz - 1) * n * n + (xx - 1) * n + yy;
cnt++;
edge[cnt].next = head[p1];
edge[cnt].to = p2;
edge[cnt].val = w;
head[p1] = cnt;
return;
}
void build() {
scanf("%d %d %d %d %d", &n, &k, &a, &b, &c);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &oil);
for (int l = 1; l <= k; l++) {
addedge(i, j, l, i, j, l + 1, 0);
}
if (oil) {
for (int l = 2; l <= k + 1; l++) {
addedge(i, j, l, i, j, 1, a);
}
if (i < n)
addedge(i, j, 1, i + 1, j, 2, 0);
if (j < n)
addedge(i, j, 1, i, j + 1, 2, 0);
if (i > 1)
addedge(i, j, 1, i - 1, j, 2, b);
if (j > 1)
addedge(i, j, 1, i, j - 1, 2, b);
} else {
for (int l = 2; l <= k + 1; l++) {
addedge(i, j, l, i, j, 1, a + c);
}
for (int l = 1; l <= k; l++) {
if (i < n)
addedge(i, j, l, i + 1, j, l + 1, 0);
if (j < n)
addedge(i, j, l, i, j + 1, l + 1, 0);
if (i > 1)
addedge(i, j, l, i - 1, j, l + 1, b);
if (j > 1)
addedge(i, j, l, i, j - 1, l + 1, b);
}
}
}
}
return;
} //分层图最短路的存图都好恶心啊。
void dij() {
memset(dis, 0x3f, sizeof(dis));
memset(use, false, sizeof(use));
dis[1] = 0;
q.push(make_pair(0, 1));
while (!q.empty()) {
int now = q.top().second;
q.pop();
if (use[now])
continue;
use[now] = true;
for (int i = head[now]; i; i = edge[i].next) {
int y = edge[i].to;
int z = edge[i].val;
if (dis[y] > dis[now] + z) {
dis[y] = dis[now] + z;
q.push(make_pair(-dis[y], y));
}
}
}
return;
}
int main() {
build();
dij();
int ans = maxd;
for (int i = 1; i <= k + 1; i++) {
ans = min(ans, dis[n * n * i]);
}
printf("%d\n", ans);
// system("pause");
return 0;
}
質問10
質問を簡略化します。
グラフの場合、一部のエッジは無向エッジ、一部は有向エッジ、有向エッジはリングに表示されず、負の重みを持つ場合があります。
次に、開始点sを指定して、sから他のすべての点までの最短の長さを見つけます。
負の重みのエッジを持つグラフはdijksralを使用できず、データは特別な構築カードspfaを通過します。
したがって、負の重みなしでこの問題の無向エッジを使用して妖精法を導き出す必要があり、単方向エッジはリングに表示されず、接続された各ブロックでdijをシークします。
次に、トポロジカルソートを使用して、接続されたブロック間の距離を処理します。最初に、すべての無向エッジをグラフに追加します。
次に、接続されているすべてのブロックを見つけ、接続されているブロックを縮小してから、トポロジカルソートに従って有向エッジを追加します。
アルゴリズムの流れ:
- 双方向エッジをグラフに追加し、接続されているすべてのブロックを決定して、それらに色を付けます。
- グラフに一方向のエッジを追加して、接続されているすべてのブロックのインディグリーとアウトディグリーを決定します。\(S \)が配置されている接続されたブロックのインディグリーが\(0 \)である場合のみ、解決策があります。
- トポロジカルソートを開始すると、初期キュー\(q \)に\(c [S] \)通信ブロックのみがあり、\(dist \)配列が同時に作成されます\(dist [s] = 0 \)。
- Unicomブロックの先頭を継続的に取り出し、Unicomブロック(dij \)でヒープ最適化を実行します。
- Unicomブロックのすべてのノードをヒープに追加します。
- \(d [x] \)の最小ノードをヒープから削除します。\(x \)がすでに最短のセットにある場合は、続行します。
- x \((x、y、z)\)のすべてのエッジをトラバースしてリラックスします。\(y \)が接続ブロック内にあり、\(y \)が更新されている場合は、\(y \)をヒープに挿入します。
- 場合\(Y \)は、他のブロックユニコム、次いで\([C [Y]に- \)に還元すると、(0 \)\、PUT \(C [Y] \)は、尾部を添加しました。
2つの接続されたブロックを接続することの負の重み側はここでは処理されないため、疑問があるかもしれません。
実際、これについてはすでに扱っています(dumbfounding.jpgの顔)。具体的には、ノートを参照してください。
その後、脱線することができます。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cmath>
#include <queue>
#define INF 0x3f3f3f3f
#define N 25010
#define M 150010
using namespace std;
int n, m1, m2, s;
int ru[N], dis[N], col[N], head[N], cnt = 0, totc = 0;
bool vis[N];
struct Edge {
int nxt, to, val;
} ed[M];
priority_queue<pair<int, int> > q;
queue<int> qq;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void addedge(int x, int y, int z) {
cnt++;
ed[cnt].nxt = head[x];
ed[cnt].to = y;
ed[cnt].val = z;
head[x] = cnt;
return;
}
void dfs(int x) {
for (int i = head[x]; i; i = ed[i].nxt) {
int y = ed[i].to;
if (!col[y]) {
col[y] = totc;
dfs(y);
}
}
return;
}
void dij() {
while (!q.empty()) {
int now = q.top().second;
q.pop();
if (vis[now])
continue;
vis[now] = true;
for (int i = head[now]; i; i = ed[i].nxt) {
int y = ed[i].to, z = ed[i].val;
if (dis[y] > dis[now] + z) {
dis[y] = dis[now] + z;
//这里是先松弛,在判断是否在一个连通块。
//也就是说,当遇到连接两个连通块的负权边时,会同样进行松弛但不会入队。
//虽然 Dijkscal 不能处理负权边,但是如果不进队仅仅是松弛操作,它还是可以胜任的。
//这里就巧妙的讲负权边也包括在内了。
if (col[now] == col[y])
q.push(make_pair(-dis[y], y));
}
if (col[now] != col[y] && !--ru[col[y]])
qq.push(col[y]);
}
}
return;
}
int main() {
n = read();
m1 = read();
m2 = read();
s = read();
int x, y, z;
for (int i = 1; i <= m1; i++) {
x = read();
y = read();
z = read();
addedge(x, y, z);
addedge(y, x, z);
}
for (int i = 1; i <= n; i++) {
if (!col[i]) {
col[i] = ++totc;
dfs(i);
}
}
for (int i = 1; i <= m2; i++) {
x = read();
y = read();
z = read();
addedge(x, y, z);
++ru[col[y]];
}
qq.push(col[s]);
for (int i = 1; i <= totc; i++)
if (!ru[i])
qq.push(i);
memset(dis, 127, sizeof(dis));
memset(vis, false, sizeof(vis));
dis[s] = 0;
while (!qq.empty()) {
int i = qq.front();
qq.pop();
for (int j = 1; j <= n; j++)
if (col[j] == i)
q.push(make_pair(-dis[j], j));
dij();
}
for (int i = 1; i <= n; i++) {
if (dis[i] > INF)
printf("NO PATH\n");
else
printf("%d\n", dis[i]);
}
return 0;
}