zoukankan      html  css  js  c++  java
  • 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_{uin U} frac{1}{|U|}cdot frac{1}{mathrm{deg(u)}} cdot[(u, v)in E])。把所有 (p(v) eq 0) 的点(即与 (U) 中至少一个点有连边的点)存起来,记为集合 (V = {v | p(v) eq 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_{vin(V cap W)} p(v)})。同时,(d_2) 发生的概率是 (sum_{vin(V cap W)} p(v))。所以 (b) 的答案是:(sum_{d_2} frac{max_{v in (V cap W)}{p(v)}}{sum_{vin(V cap W)} p(v)} imes (sum_{vin(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;
    }
    
  • 相关阅读:
    LightOJ 1370 Bi-shoe and Phi-shoe
    CF410div2 B. Mike and strings
    CF410div2 A. Mike and palindrome
    CF410div2 D. Mike and distribution
    CF798 C. Mike and gcd problem
    LightOJ1282 Leading and Trailing
    入门-2
    入门-1
    铅笔,用用,舍不得放下
    《快乐读书 轻松理财》书摘
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14347422.html
Copyright © 2011-2022 走看看