CF679D Bear and Chase

CF679D Bear and Chase

题目链接

本题题解

记图的边集为 \(E\)

先用 floyd 算法预处理出任意两点之间的距离。时间复杂度 \(\mathcal{O}(n^3)\)

记两次查询的节点分别为 \(a\), \(b\),两次查询得到的结果分别为 \(d_1, d_2\)

  • 枚举 \(a\)。计算出第一步选 \(a\) 时的答案。因为我们要求最优策略下的答案,所以对所有 \(a\) 的情况取最大值即可。
    • 枚举 \(d_1\)。考虑所有距离 \(a\)\(d_1\) 的节点,记为点集 \(U = \{u | \mathrm{dis}(u, a) = d_1\}\)。则得到回答 "\(d_1\)" 的概率是 \(\frac{|U|}{n}\)。求出 \(d_1\) 的答案后,乘以这个概率,累加起来,就是当前 \(a\) 的答案了。
      • 预处理出第二天罪犯来到 \(v\) 的概率 \(p(v)\)。具体来说,\(p(v) = \sum_{u\in U} \frac{1}{|U|}\cdot \frac{1}{\mathrm{deg(u)}} \cdot[(u, v)\in E]\)。把所有 \(p(v)\neq 0\) 的点(即与 \(U\) 中至少一个点有连边的点)存起来,记为集合 \(V = \{v | p(v)\neq 0\}\)
      • 现在已经得到了回答 \(d_1\)。考虑我们接下来的最优策略,是两种情况的较大者:要么在 \(U\) 中随便猜一个点,并结束游戏,获胜的概率是 \(\frac{1}{|U|}\)。要么继续询问。
      • 如果继续询问。枚举接下来要询问的点 \(b\)。那这种情况下,获胜的概率就是所有 \(b\) 的答案的最大值。对每个 \(b\)
        • 枚举得到的回答 \(d_2\)。考虑距离 \(b\)\(d_2\) 的点,记为集合 \(W = \{w | \mathrm{dis}(w, b) = d_2\}\)。在知道了 \(d_2\) 后,我们有唯一的最优策略,且获胜的概率是 \(\frac{\max_{v \in (V \cap W)}\{p(v)\}}{\sum_{v\in(V \cap W)} p(v)}\)。同时,\(d_2\) 发生的概率是 \(\sum_{v\in(V \cap W)} p(v)\)。所以 \(b\) 的答案是:\(\sum_{d_2} \frac{\max_{v \in (V \cap W)}\{p(v)\}}{\sum_{v\in(V \cap W)} p(v)}\times (\sum_{v\in(V \cap W)} p(v)) = \max_{v \in (V \cap W)}\{p(v)\}\)。所以我们只需要在枚举 \(d_2\) 之前,先枚举一遍 \(v\),就能预处理出每个 \(d_2\) 的答案。然后再扫描所有 \(d_2\),把这些答案加起来。

上述的做法看起来是四重循环。但对于同一个 \(a\),在所有 \(d_1\)\(U\) 集合的大小之和为 \(n\)\(V\) 集合里的点与 \(a\) 的距离要么是 \(d_1 + 1\),要么是 \(d_1 - 1\),所以 \(V\) 集合的大小之和也是 \(\mathcal{O}(n)\) 的。发现我们真正枚举的只有 \(a, b, V\),时间复杂度是 \(\mathcal{O}(n^3)\) 的。

具体可以见代码。

参考代码

// problem: CF679D
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 400;
const int INF = 1e9;

int n, m;
vector<int> G[MAXN + 5];
int dis[MAXN + 5][MAXN + 5];

double p[MAXN + 5];
bool vis[MAXN + 5];
vector<int> calc_probability_for_each_city(const vector<int>& vec_u) {
	for (int i = 1; i <= n; ++i) {
		p[i] = 0;
		vis[i] = 0;
	}
	vector<int> vec_v;
	for (int i = 0; i < SZ(vec_u); ++i) {
		int u = vec_u[i];
		for (int j = 0; j < SZ(G[u]); ++j) {
			int v = G[u][j];
			p[v] += 1.0 / SZ(vec_u) / SZ(G[u]);
			if (!vis[v]) {
				vis[v] = 1;
				vec_v.push_back(v);
			}
		}
	}
	return vec_v;
}

double pmax[MAXN + 5];
double consider_tomorrow(const vector<int>& vec_v) {
	double res = 0;
	for (int i = 0; i < n; ++i) {
		pmax[i] = 0;
	}
	for (int b = 1; b <= n; ++b) { // 第二次询问的节点
		// 选好 b 后, 会问到一个距离 d2
		// 我们根据 d2 去选择最终的猜测: c. 即: 每个 d2 对应了一种选 c 的策略
		// 具体来说就是选择该 d2 里概率 p 最大的点作为 c
		// 所以此处先对每种距离 d2, 预处理出它对应的节点的 p 的最大值
		
		for (int i = 0; i < SZ(vec_v); ++i) {
			int v = vec_v[i];
			ckmax(pmax[dis[v][b]], p[v]);
		}
		
		double res_b = 0;
		for (int i = 0; i < SZ(vec_v); ++i) {
			int v = vec_v[i];
			int d2 = dis[v][b];
			
			res_b += pmax[d2];
			pmax[d2] = 0;
		}
		ckmax(res, res_b);
	}
	return res;
}
double ask_immediately(const vector<int>& vec_u) {
	return 1.0 / SZ(vec_u);
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			dis[i][j] = (i == j ? 0 : INF);
	for (int i = 1; i <= m; ++i) {
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
		dis[u][v] = dis[v][u] = 1;
	}
	for (int k = 1; k <= n; ++k) { // 中间点
		for (int i = 1; i <= n; ++i) { // 起点
			for (int j = 1; j <= n; ++j) { // 终点
				ckmin(dis[i][j], dis[i][k] + dis[k][j]);
			}
		}
	}
	double ans = 0;
	for (int a = 1; a <= n; ++a) { // 第一次询问的节点
		double ans_a = 0;
		for (int d1 = 0; d1 < n; ++d1) { // 第一次得到的答案
			vector<int> vec_u;
			double p_d1 = 0;
			for (int u = 1; u <= n; ++u) if (dis[a][u] == d1) {
				vec_u.push_back(u);
				p_d1 += 1;
			}
			if (!SZ(vec_u))
				continue;
			
			p_d1 /= (double)n;
			
			double ans_a_d1 = 0;
			vector<int> vec_v = calc_probability_for_each_city(vec_u);
			ans_a_d1 = max(consider_tomorrow(vec_v), ask_immediately(vec_u));
			ans_a += p_d1 * ans_a_d1; // 有 p 的概率, 进入 d1 这种情况
			                          // 在 d1 的情况下, 选取最优策略能得到 ans_a_d1 这一答案
		}
		ckmax(ans, ans_a); // 选取最优策略
	}
	cout << setiosflags(ios :: fixed) << setprecision(10) << ans << endl;
	return 0;
}
posted @ 2021-01-29 22:28  duyiblue  阅读(165)  评论(0编辑  收藏  举报