Kevinrzy103874的博客

Kevinrzy103874的博客

动态线条
动态线条end
code: {

专注于分享信息学竞赛技巧、知识点、模拟赛及一些题目题解,又有着当码农的乐趣,有时还会写写比赛游记等等。

模拟赛Atcoder Beginner Contest 429 & Polaris.AI Programming Contest 2025比赛记录&题解(D、E题)

D.On AtCoder Conference

问题陈述

有一个周长为 \(M\) 的池塘,池塘边有一间小屋和 \(N\) 人。
对于实数 \(x\) \((0\leq x <M)\) ,定义点 \((0\leq x <M)\)\((0\leq x <M)\) ,把点 \(x\) 定义为小屋顺时针方向距离 \(x\) 的位置。
\(i\) 个人位于第 \(A_i\) 点。**可能会有多人站在同一位置。

此外,还给出了一个不大于 \(N\) 的整数 \(C\) 。对于 \(i=0,1,\ldots,M-1\) ,定义 \(X_i\) 如下:

  1. 高桥从点 \((i+0.5)\) 开始顺时针方向移动。
    1. 只要高桥遇到的人的总数少于 \(C\) ,他就继续(顺时针)移动,当总数达到或超过 \(C\) 时停止移动。在这里,"在 \(y\) 点遇到一个人 "意味着高桥到达了 \(y\) 点。
  2. \(X_i\) 为高桥在停止前遇到的人数。在这里,如果高桥停下的点有多人,那么那里的人都算作他遇到的人,特别是 \(X_i\) 可能大于 \(C\)

\(X_i\) 大于 \(i=0,1,\ldots,M-1\) 的和,即 \(\displaystyle\sum_{i=0}^{M-1} X_i\)

限制因素

  • \(1\leq N\leq 5\times 10^5\)
  • \(1\leq M\leq 10^{12}\)
  • \(0\leq A_i\leq M-1\)
  • \(1\leq C\leq N\)
  • 所有输入值均为整数。

输入

输入内容由标准输入法提供,格式如下

\[N \ \ M \ \ C \\ A_1 \ \ A_2 \ \ \ldots \ \ A_N \]

输出

\(X_i\)\(i=0,1,\ldots,M-1\) 之和打印在一行上。

输入样本 1

5 3 2
1 2 1 0 1

样本输出 1

9

\(i=0\) 时,高桥从点 \(0.5\) 开始顺时针方向移动。然后,会发生以下情况:

  • \(1\) 点,他遇到了 第 \(1\) 个、 第 \(3\) 个和 第 \(5\) 个三个人,总共遇到了三个人,目前遇到的总人数为 \(3\) 。这个数字不小于 \(C=2\) ,所以高桥就此打住。因此, \(X_0=3\)

\(i=1\) 时,高桥从点 \(1.5\) 开始顺时针方向移动。然后,会发生以下情况:

  • \(2\) 点,他遇到了 第 \(2\) 个人。到目前为止遇到的总人数为 \(1\) ,因此他继续移动。
  • \(0\) 点,他遇到了 第 \(4\) 个人,目前遇到的总人数为 \(2\) 。这个数字不小于 \(C=2\) ,所以高桥停在了这里。因此, \(X_1=2\)

\(i=2\) 时,高桥从点 \(2.5\) 开始顺时针方向移动。然后,会发生以下情况:

  • \(0\) 点,他遇到了 第 \(4\) 个人。到目前为止,他遇到的总人数为 \(1\) ,因此他继续移动。
  • \(1\) 点,他遇到了 第 \(1\) 个、 第 \(3\) 个和 第 \(5\) 个人,一共三个人,目前遇到的总人数为 \(4\) 。这个数字不小于 \(C=2\) ,所以高桥就此打住。因此, \(X_2=4\)

因此,答案为 \(X_0+X_1+X_2=3+2+4=9\)

输入样本 2

1 1000000000000 1
1

输出示例 2

1000000000000

无论起始位置如何,当高桥遇到池塘周围唯一站立的人时,他都会停下来,此人位于 \(1\) 点。
因此, \(X_i=1\)\(i\) 无关,答案为 \(10^{12}\)


E.Hit and Away

问题陈述

给你一个简单相连的无向图 \(G\) ,其中有 \(N\) 个顶点和 \(M\) 条边。
\(G\) 的顶点和边分别编号为顶点 \(1,2,\ldots,N\) 和边 \(1,2,\ldots,M\) ,边 \(i\) 连接顶点 \(U_i\)\(V_i\)
您可以在边 \(1\) 所连接的顶点之间双向移动。

此外,每个顶点要么是安全的,要么是危险的,这种状态由长度为 \(N\) 的字符串 \(S\) 表示,字符串由 SD 组成。
具体来说,当 \(S\)\(i\) -th字符 \((1\leq i\leq N)\)S时,顶点 \(i\) 是安全的;当 \(i\)D时,顶点 \(i\) 是危险的。
可以保证至少有两个安全顶点和至少一个危险顶点。

求每个危险顶点 \(v\) 的值:

从某个安全顶点出发,经过 \(v\) 并移动到与出发顶点**不同的安全顶点所需的最短时间。

限制因素

  • \(3\leq N\leq 2\times 10^5\)
  • \(N-1\leq M\leq 2\times 10^5\)
  • \(1\leq U_i,V_i\leq N\)
  • \(U_i\neq V_i\)
  • 如果 \(i\neq j\) ,那么 \(\{ U_i,V_i \}\neq \{ U_j,V_j \}\)
  • \(S\) 是长度为 \(N\) 的字符串,由 SD 组成。
  • \(N,M,U_i,V_i\) 都是整数。
  • \(G\) 是连通的。
  • 至少有两个安全顶点。
  • 至少有一个危险顶点。

输入

输入内容由标准输入法提供,格式如下

\[N \ \ M \\ U_1 \ \ V_1 \\ U_2 \ \ V_2 \\ \vdots \\ U_M \ \ V_M \\ S \]

输出

假设 \(K\)\(G\) 中的危险顶点数,并打印出 \(K\) 行。
\(i\) / \((1\leq i\leq K)\) 行,打印当危险顶点按顶点数升序排列时, \(i\) / \(i\) 个危险顶点的答案。

输入样本 1

5 5
1 2
1 3
2 3
3 4
4 5
SSDDS

样本输出 1

2
3

危险顶点是(按顶点编号升序排列)顶点 \(3\)\(4\)

对于顶点 \(3\) ,从顶点 \(1\) 移动到顶点 \(\to\) 。顶点 \(\to\) 移动到顶点 \(3\)\(\to\) 顶点 \(2\) (例如)满足条件。
这一移动所需的时间为 \(2\) ,这是最小值。
因此,在 第 \(1\) 个 行打印 \(2\)

对于顶点 \(4\) ,从顶点 \(1\) 开始移动 \(\to\) 顶点 \(3\) \(\to\) 顶点 \(\to\) 顶点 \(4\) \(\to\) 顶点 \(5\) (例如)满足条件。
这个移动所需的时间为 \(3\) ,而没有任何一种移动方式能在满足条件的同时花费 \(2\) 或更少的时间,因此这是最小值。
因此,在 第 \(2\) 个行打印 \(3\)

输入样本 2

3 2
1 2
2 3
SSD

输出示例 2

3

危险顶点是顶点 \(3\)

从顶点 \(1\) 开始移动 \(\to\) 顶点 \(2\)\(\to\) 顶点 \(3\)\(\to\) 顶点 \(2\) (例如(例如)满足条件。
这一移动所需的时间为 \(3\) ,这也是最小值。
请注意,像顶点 \(2\) 这样的移动顶点 \(\to\) 顶点 \(3\) 等运动。 \(\to\) 顶点 \(2\) 不满足终点 "与起始顶点不同 "的条件。


D.题解

好的,这道题其实是一个环形计数问题,结合了前缀和双指针技巧。我会一步步带你理解。


1. 问题重述

我们有一个环形池塘(周长 \(M\)),上面有 \(N\) 个人站在整数位置 \(A_i\)\(0 \le A_i < M\))。

对于每个起始点 \(i + 0.5\)\(i = 0, 1, \dots, M-1\)),高桥顺时针走,直到遇到的人数 \(\ge C\) 为止,记录下他总共遇到的人数 \(X_i\)

要求:

\[\sum_{i=0}^{M-1} X_i \]


2. 关键观察

环形处理

由于池塘是环形的,我们可以把位置复制一份,即把 \(A\) 数组复制一份,每个元素加上 \(M\),这样就把环形变成了线性

例如:

  • 原数组 \(A = [1, 2, 1, 0, 1]\)
  • 复制后:\([1, 2, 1, 0, 1, 4, 5, 4, 3, 4]\)(当 \(M=3\) 时)

这样,从任意起点出发,走一圈就相当于在扩展数组上走一段连续区间。


问题转化

对于起点 \(i + 0.5\),相当于在环形上从 \(i+0.5\) 出发,顺时针走,直到遇到第 \(C\) 个人(含起点之后遇到的人)。

设起点 \(i+0.5\) 在环上的位置是 \(start = i+0.5\)

在扩展数组(长度 \(2N\))中,我们找到第一个位置 \(p\),使得区间 \([start, p]\) 内的人数 \(\ge C\)

那么 \(X_i\) = 区间 \([start, p]\) 内的人数。


如何高效计算

如果直接枚举 \(i\)\(0\)\(M-1\)\(M\) 最大 \(10^{12}\),显然不可行。

  • 突破口:
    • \(X_i\) 的值只取决于起点 \(i+0.5\) 落在环上的哪两个人之间
    • 也就是说,\(X_i\)一段连续起点区间内是相同的。

实际上,更简单的方法:

  1. 对 A 排序。
  2. 复制 A 到 A2,每个元素加 M。
  3. 前缀和 P。
  4. 对每个 i,在 A2 中二分查找第一个位置,使得 A2[r] - (i+0.5) 对应的人数区间和 >= C。
  5. 但这样还是 O(M log N),不可行。

所以必须分段
相邻两个人之间的起点,X_i 相同。
所以只需对每段计算一次 X,乘以段长度。


3. 最终正确代码思路

/*
oier:任思瑞
age:12
*/
#include <bits/stdc++.h>
using namespace std;
#define fre(c) freopen(c".in","r",stdin);freopen(c".out","w",stdout);
#define ll long long
#define endl "\n"
#define ios ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define cst const
ll n, m, c, ans, pos, num, idx, len;
vector<ll> a, d, cnt, d2, cnt2, p, r;
inline ll read() {
	char c;
	ll sum = 0, f = 1;
	c = getchar();
	while (!isdigit(c)) {
		if (c == '-')
			f *= -1;
		c = getchar();
	}
	while (isdigit(c)) {
		sum = sum * 10 + (c - '0');
		c = getchar();
	}
	return sum * f;
}
int main() {
//	ios
//	fre("")
	n = read();
	m = read();
	c = read();
	a.resize(n);
	for (ll i = 0; i < n; i ++) a[i] = read();
	sort(a.begin(), a.end());
	pos = a[0];
	num = 1;
	for (ll i = 1; i < n; i ++) {
		if (a[i] == pos) num ++;
		else {
			d.push_back(pos);
			cnt.push_back(num);
			pos = a[i];
			num = 1;
		}
	}
	d.push_back(pos);
	cnt.push_back(num);
	ll sz = d.size();
	d2.resize(2 * sz);
	cnt2.resize(2 * sz);
	for (ll i = 0; i < sz; i ++) {
		d2[i] = d[i];
		cnt2[i] = cnt[i];
	}
	for (ll i = 0; i < sz; i ++) {
		d2[i + sz] = d[i] + m;
		cnt2[i + sz] = cnt[i];
	}
	p.resize(2 * sz + 1);
	for (ll i = 1; i <= 2 * sz; i ++) p[i] = p[i - 1] + cnt2[i - 1];
	r.resize(sz);
	for (ll j = 0; j < sz; j ++) {
		while (idx < 2 * sz && p[idx + 1] - p[j] < c) {
			idx ++;
		}
		r[j] = idx;
	}
	for (ll j = 0; j < sz; j ++) {
		len = 0;
		if (j == 0) {
			len = m - d[sz - 1] + d[0];
		} else {
			len = d[j] - d[j - 1];
		}
		ans += len * (p[r[j] + 1] - p[j]);
	}
	cout << ans;
	return 0;
}

4. 总结

这道题的核心是:

  1. 环形转线性:通过复制数组处理环形结构。
  2. 分段处理:相邻人之间的起点共享相同的 \(X_i\)
  3. 前缀和+双指针:快速确定每个起点区间对应的停止位置和人数。
  4. 避免枚举:利用区间长度直接计算总和,将复杂度从 \(O(M)\) 降到 \(O(N)\)

E.题解

前置知识:多源Dijkstra

多源Dijkstra算法详解

1. 算法原理

1.1 单源 vs 多源Dijkstra

单源Dijkstra:从单个起点出发,计算到图中所有其他顶点的最短路径。

多源Dijkstra:从多个起点同时出发,计算每个顶点到最近起点的最短路径。

1.2 核心思想

多源Dijkstra可以看作是单源Dijkstra的自然扩展

  • 将所有起点同时加入优先队列
  • 每个起点初始距离为0
  • 算法执行过程中,每个顶点记录的是到任意起点的最短距离
  • 同时可以记录是哪个起点产生了这个最短距离

1.3 数学表达

设起点集合为 \(S = \{s_1, s_2, ..., s_k\}\),对于任意顶点 \(v\),我们计算:

\[d[v] = \min_{s \in S} dist(s, v) \]

其中 \(dist(s, v)\) 表示从起点 \(s\) 到顶点 \(v\) 的最短路径长度。


2. 算法实现

2.1 基础版本

vector<ll> multiSourceDijkstra(vector<vector<ll>>& graph, vector<ll>& sources) {
    ll n = graph.size();
    vector<ll> dist(n, INF);
    priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> pq;
    
    // 所有起点入队
    for (ll src : sources) {
        dist[src] = 0;
        pq.push({0, src});
    }
    
    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();
        
        if (d > dist[u]) continue;
        
        for (ll v : graph[u]) {
            ll nd = d + 1;  // 假设边权为1
            if (nd < dist[v]) {
                dist[v] = nd;
                pq.push({nd, v});
            }
        }
    }
    
    return dist;
}

2.2 记录源点版本

struct Node {
    ll dist, node, source;
    bool operator>(const Node& other) const {
        return dist > other.dist;
    }
};

vector<pair<ll, ll>> multiSourceWithSource(vector<vector<ll>>& graph, vector<ll>& sources) {
    ll n = graph.size();
    vector<ll> dist(n, INF);
    vector<ll> source_id(n, -1);  // 记录是哪个起点产生的最短距离
    priority_queue<Node, vector<Node>, greater<Node>> pq;
    
    for (ll src : sources) {
        dist[src] = 0;
        source_id[src] = src;
        pq.push({0, src, src});
    }
    
    while (!pq.empty()) {
        auto [d, u, src] = pq.top();
        pq.pop();
        
        if (d > dist[u]) continue;
        
        for (ll v : graph[u]) {
            ll nd = d + 1;
            if (nd < dist[v]) {
                dist[v] = nd;
                source_id[v] = src;  // 记录源点
                pq.push({nd, v, src});
            }
        }
    }
    
    return {dist, source_id};
}

3. 算法正确性证明

3.1 贪心选择性质

多源Dijkstra保持了单源Dijkstra的贪心性质:

  • 每次从优先队列中取出的是当前已知的全局最小距离
  • 这个最小距离不会再被更新
  • 保证了每个顶点的最短距离只被确定一次

3.2 最优子结构

对于任意顶点 \(v\),设其最短路径为 \(s \to ... \to u \to v\),那么:

  • \(s \to ... \to u\) 也是最短路径
  • 算法会先确定 \(u\) 的最短距离,再通过 \(u\) 更新 \(v\)

3.3 数学归纳

基础情况:所有起点的距离为0,正确。

归纳步骤:假设所有已确定最短距离的顶点都是正确的,那么通过它们更新的邻居也会得到正确的最短距离。


4. 复杂度分析

4.1 时间复杂度

  • 每个顶点:最多入队一次
  • 每条边:最多被松弛一次
  • 优先队列操作\(O(\log N)\) 每次
  • 总复杂度\(O((N + M) \log N)\)

与单源Dijkstra相同!

4.2 空间复杂度

  • 图存储:\(O(N + M)\)
  • 距离数组:\(O(N)\)
  • 优先队列:\(O(N)\)
  • 总空间\(O(N + M)\)

5. 应用场景

5.1 最近设施问题

  • 多个商店,求每个居民到最近商店的距离
  • 多个消防站,求每个建筑到最近消防站的距离

5.2 网络广播

  • 多个广播源,求每个节点收到信号的最短时间

5.3 竞争性设施定位

  • 分析多个竞争对手设施的服务范围

6. 例题实战

例题1:多源最短路径(基础)

题目:图中有 \(k\) 个起点,求每个顶点到最近起点的距离。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll INF = 1e18;

vector<ll> multiSourceBFS(vector<vector<ll>>& graph, vector<ll>& sources) {
    ll n = graph.size();
    vector<ll> dist(n, INF);
    queue<ll> q;
    
    for (ll src : sources) {
        dist[src] = 0;
        q.push(src);
    }
    
    while (!q.empty()) {
        ll u = q.front();
        q.pop();
        
        for (ll v : graph[u]) {
            if (dist[v] == INF) {
                dist[v] = dist[u] + 1;
                q.push(v);
            }
        }
    }
    
    return dist;
}

int main() {
    ll n, m, k;
    cin >> n >> m >> k;
    
    vector<vector<ll>> graph(n + 1);
    for (ll i = 0; i < m; i++) {
        ll u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    
    vector<ll> sources(k);
    for (ll i = 0; i < k; i++) {
        cin >> sources[i];
    }
    
    vector<ll> dist = multiSourceBFS(graph, sources);
    
    for (ll i = 1; i <= n; i++) {
        cout << "顶点 " << i << " 到最近起点的距离: " << dist[i] << endl;
    }
    
    return 0;
}

例题2:危险顶点问题(进阶)

这就是我们刚才讨论的题目,需要记录两个不同源点的最短距离。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll INF = 1e18;

int main() {
    ll n, m;
    cin >> n >> m;
    
    vector<vector<ll>> graph(n + 1);
    for (ll i = 0; i < m; i++) {
        ll u, v;
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    
    string s;
    cin >> s;
    s = " " + s;  // 1-indexed
    
    vector<ll> d1(n + 1, INF), d2(n + 1, INF);
    vector<ll> s1(n + 1, -1), s2(n + 1, -1);
    
    // 使用tuple: (距离, 顶点, 源点)
    using Node = tuple<ll, ll, ll>;
    priority_queue<Node, vector<Node>, greater<Node>> pq;
    
    // 所有安全顶点作为起点
    for (ll i = 1; i <= n; i++) {
        if (s[i] == 'S') {
            d1[i] = 0;
            s1[i] = i;
            pq.push({0, i, i});
        }
    }
    
    while (!pq.empty()) {
        auto [dist_u, u, source] = pq.top();
        pq.pop();
        
        // 如果这个距离已经不是最短或次短,跳过
        if (dist_u != d1[u] && dist_u != d2[u]) continue;
        
        for (ll v : graph[u]) {
            ll new_dist = dist_u + 1;
            
            // 情况1:新的最短距离
            if (new_dist < d1[v]) {
                // 如果源点不同,更新次短信息
                if (s1[v] != source) {
                    d2[v] = d1[v];
                    s2[v] = s1[v];
                }
                d1[v] = new_dist;
                s1[v] = source;
                pq.push({new_dist, v, source});
            }
            // 情况2:相等距离但不同源点,可能更新次短
            else if (new_dist == d1[v] && s1[v] != source) {
                if (new_dist < d2[v]) {
                    d2[v] = new_dist;
                    s2[v] = source;
                    pq.push({new_dist, v, source});
                }
            }
            // 情况3:新的次短距离(且源点不同)
            else if (new_dist < d2[v] && s1[v] != source) {
                d2[v] = new_dist;
                s2[v] = source;
                pq.push({new_dist, v, source});
            }
        }
    }
    
    // 输出结果
    vector<ll> dangerous;
    for (ll i = 1; i <= n; i++) {
        if (s[i] == 'D') {
            dangerous.push_back(i);
        }
    }
    sort(dangerous.begin(), dangerous.end());
    
    for (ll v : dangerous) {
        if (d2[v] == INF) {
            cout << -1 << endl;  // 无法找到两个不同安全顶点
        } else {
            cout << d1[v] + d2[v] << endl;
        }
    }
    
    return 0;
}

7. 算法变种与优化

7.1 多源多汇问题

不仅多个起点,还有多个终点,求起点到终点的各种组合的最短路径。

7.2 带权图的多源Dijkstra

只需修改距离更新部分:

// 假设graph存储的是(u, v, weight)
for (auto [v, w] : graph[u]) {
    ll new_dist = dist_u + w;
    // ... 其余相同
}

7.3 K近邻多源搜索

记录每个顶点到最近的K个起点的距离,而不仅仅是最近的一个。


8. 总结

多源Dijkstra是单源Dijkstra的自然推广,具有:

  1. 相同的复杂度\(O((N+M)\log N)\)
  2. 广泛的应用:设施定位、服务范围分析等
  3. 灵活的扩展:可以记录源点信息、处理多个最近点等
  4. 实现简单:只需在初始化时加入所有起点

关键理解:多源Dijkstra本质上是在同一个图上同时运行多个单源Dijkstra,通过统一的优先队列来保证全局最优性。


核心思路分析

1. 问题重述

对于每个危险顶点 \(v\),我们需要找到一条路径:

  • 起点:某个安全顶点 \(s_1\)
  • 终点:另一个安全顶点 \(s_2\)\(s_1 \neq s_2\)
  • 必须经过:危险顶点 \(v\)
  • 目标:最小化路径长度 \(dist(s_1, v) + dist(v, s_2)\)

2. 关键观察

设危险顶点 \(v\) 的答案为 \(ans_v\),则:

\[ans_v = \min_{\substack{s_1, s_2 \in \text{Safe} \\ s_1 \neq s_2}} [dist(s_1, v) + dist(v, s_2)] \]

其中 \(dist(u, v)\) 表示顶点 \(u\)\(v\) 的最短路径长度。

3. 简化思路

对于固定顶点 \(v\),我们实际上需要找到两个不同的安全顶点 \(s_1\)\(s_2\),使得:

  • \(s_1\) 是离 \(v\) 最近的安全顶点
  • \(s_2\) 是离 \(v\) 第二近的安全顶点(且与 \(s_1\) 不同)

那么:

\[ans_v = dist(s_1, v) + dist(s_2, v) \]

为什么这样是正确的?

  • 如果最近的两个安全顶点相同,我们无法满足"不同安全顶点"的要求
  • 次近的安全顶点保证了我们能够找到两个不同的安全顶点
  • 路径 \(s_1 \to v \to s_2\) 一定是最优的,因为任何其他组合都会产生更长的路径

4. 算法选择

我们需要为每个顶点计算:

  • \(d_1[v]\)\(v\) 到最近安全顶点的距离
  • \(d_2[v]\)\(v\) 到次近安全顶点的距离(且安全顶点与最近的不同)
  • \(s_1[v]\):最近安全顶点的编号
  • \(s_2[v]\):次近安全顶点的编号

使用改进的Dijkstra算法

  • 所有安全顶点同时开始搜索
  • 维护每个顶点的最短和次短距离信息
  • 确保次短距离来自不同的安全顶点

正确性证明

1. 最优性

  • \(d_1[v]\) 确实是 \(v\) 到最近安全顶点的最短距离
  • \(d_2[v]\)\(v\) 到另一个不同安全顶点的最短距离
  • 路径 \(s_1 \to v \to s_2\) 的长度为 \(d_1[v] + d_2[v]\)
  • 任何其他满足条件的路径都不会比这个更短

2. 完备性

  • 由于图是连通的,且至少有两个安全顶点
  • 每个危险顶点都能找到两个不同的安全顶点
  • 算法保证找到的是真正的最短路径

复杂度分析

  • 时间复杂度\(O((N + M) \log N)\)
    • 每个顶点最多被处理两次(最短和次短)
    • 使用优先队列,每次操作 \(O(\log N)\)
  • 空间复杂度\(O(N + M)\)
    • 存储图和距离信息

代码实现

/*
oier:任思瑞
age:12
*/
#include <bits/stdc++.h>
using namespace std;
#define fre(c) freopen(c".in","r",stdin);freopen(c".out","w",stdout);
#define ll long long
#define endl "\n"
#define ios ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define cst const
cst ll N = 2 * 1e5 + 5;
ll n, m, d1[N], d2[N], s1[N], s2[N], need;
vector<ll> g[N];
string s;
inline ll read() {
	char c;
	ll sum = 0, f = 1;
	c = getchar();
	while (!isdigit(c)) {
		if (c == '-')
			f *= -1;
		c = getchar();
	}
	while (isdigit(c)) {
		sum = sum * 10 + (c - '0');
		c = getchar();
	}
	return sum * f;
}
int main() {
//	ios
//	fre("")
	n = read();
	m = read();
	for (ll i = 0; i < m; i ++) {
		ll u = read(), v = read();
		g[u].push_back(v);
		g[v].push_back(u);
	}
	cin >> s;
	s = " " + s;
	for (ll i = 1; i <= n; i ++) {
		d1[i] = 1e18;
		d2[i] = 1e18;
		s1[i] = -1;
		s2[i] = -1;
	}
	priority_queue<tuple<ll, ll, ll>, vector<tuple<ll, ll, ll>>, greater<tuple<ll, ll, ll>>> q;
	for (ll i = 1; i <= n; i ++) {
		if (s[i] == 'S') {
			d1[i] = 0;
			s1[i] = i;
			q.push({
				0, 
				i, 
				i
			});
		}
	}
	while (!q.empty()) {
		auto [d, u, pos] = q.top();
		q.pop();
		if (d != d1[u] && d != d2[u]) continue;
		for (ll v : g[u]) {
			need = d + 1;
			if (need < d1[v]) {
				if (s1[v] != pos) {
					d2[v] = d1[v];
					s2[v] = s1[v];
				}
				d1[v] = need;
				s1[v] = pos;
				q.push({
					need, 
					v, 
					pos
				});
			} else if (need == d1[v]) {
				if (s1[v] != pos && need < d2[v]) {
					d2[v] = need;
					s2[v] = pos;
					q.push({
						need, 
						v, 
						pos
					});
				}
			} else if (need < d2[v]) {
				if (s1[v] != pos) {
					d2[v] = need;
					s2[v] = pos;
					q.push({
						need, 
						v, 
						pos
					});
				}
			}
		}
	}
	vector<ll> ans;
	for (ll i = 1; i <= n; i ++) {
		if (s[i] == 'D') {
			ans.push_back(i);
		}
	}
	sort(ans.begin(), ans.end());
	for (ll v : ans) {
		cout << d1[v] + d2[v] << endl;
	}
	return 0;
}

总结

本题的关键在于将复杂的最优路径问题转化为寻找最近的两个不同安全顶点的问题。通过多源Dijkstra算法同时维护最短和次短路径信息,我们能够高效地解决这个看似复杂的问题。

核心技巧

  1. 多源起点同时搜索
  2. 维护最短和次短两条路径
  3. 确保两条路径来自不同的源点
  4. 利用优先队列保证最优性
posted @ 2025-10-25 21:53  Kevinrzy103874  阅读(87)  评论(0)    收藏  举报