[SDOI2012] 走迷宫 题解
前言
题目链接:洛谷;Hydro & bzoj。
题意简述
有向图中,求起点到终点的期望步数。若期望不存在,输出 INF。
保证强连通分量的大小不超过 \(100\)。
题目分析
首先来想想什么情况下期望不存在。很显然是,从起点能走到一个点,而该点永远走不到终点,当然,是在走到终点马上停下的前提下。转化一下,就是从起点开始 BFS,如果遇到一个点,没有出度,并且这个点还不是终点,那么这种情况下应该输出 INF。但是,很多人都想错了这一步判 INF,详见此帖。
代码实现起来很简单:
bool vis[10010];
bool check() {
queue<int> Q;
Q.push(S), vis[S] = true;
bool can = false;
while (!Q.empty()) {
int now = Q.front(); Q.pop();
if (now == T) {
can = true;
continue;
}
if (!xym.head[now]) return false;
for (int i = xym.head[now], to; to = xym[i].to, i; i = xym[i].nxt) {
if (vis[to]) continue;
vis[to] = true;
Q.push(to);
}
}
return can;
}
接下来考虑如何求期望步数。
一个套路的想法,记 \(f_i\) 为从 \(i\) 到终点的期望步数,边界 \(f_t = 0\),答案就是 \(f_s\)。转移就是在出边里等概率选择一条边。
\]
由于存在环形转移,所以使用高斯消元解方程组就行了。注意到,在预处理增广矩阵的时候,对于 \(xym \to yzh\) 这条边,如果 \(xym = t\),就不做处理;如果在之前 check 的时候没走到过 \(xym\),即 \(\operatorname{vis}[xym] = \text{false}\),也不要添加到矩阵里。
由于时间复杂度 \(\Theta(n^3)\),能拿到 \(70\) 分。考虑如何优化。
注意到之所以要用高斯消元,是因为存在环形转移。如果是在序列上,或者换句话说,在一个 DAG 上,我们直接 DP 就行了。所以考虑用 tarjan 缩点,强联通分量里高斯消元,分量外拓扑排序直接期望 DP。这么做正确性体现在题目中保证强连通分量的大小不超过 \(100\),时间复杂度 \(\Theta(\sum siz^3) \leq \mathcal{O}(n\max ^ 2siz)\)。
具体地,我们反向跑拓扑。对于当前强联通分量里的每一个点连出的边,如果对方不是同一个强联通分量,则已经被我们计算过了,加到右边常数里;反之处理到左边系数矩阵里。
代码
挺快的,卡卡常最优解。
// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main() { return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
template <size_t N, size_t M>
int guass(int, int, double [M][N], double [M], double [N]);
const double eps = 1e-10;
int n, m, S, T;
int U[1000010], V[1000010];
int du[10010];
struct Graph{
struct node{
int to, nxt;
} edge[1000010 << 1];
int eid, head[10010];
inline void add(int u, int v){
edge[++eid] = {v, head[u]};
head[u] = eid;
}
inline node & operator [] (const int x){
return edge[x];
}
} xym, yzh;
bool vis[10010];
bool check() {
queue<int> Q;
Q.push(S), vis[S] = true;
bool can = false;
while (!Q.empty()) {
int now = Q.front(); Q.pop();
if (now == T) {
can = true;
continue;
}
if (!xym.head[now]) return false;
for (int i = xym.head[now], to; to = xym[i].to, i; i = xym[i].nxt) {
if (vis[to]) continue;
vis[to] = true;
Q.push(to);
}
}
return can;
}
int dfn[10010], low[10010], timer;
int sccno[10010], scc_cnt;
int stack[10010], top;
bool in_stack[10010];
vector<int> scc[10010];
int whr[10010];
double key[110][110], val[110], res[10010][110];
void tarjan(int now) {
dfn[now] = low[now] = ++timer, in_stack[stack[++top] = now] = true;
for (int i = xym.head[now]; i; i = xym[i].nxt) {
int to = xym[i].to;
if (dfn[to] == 0) tarjan(to), low[now] = min(low[now], low[to]);
else if (in_stack[to]) low[now] = min(low[now], dfn[to]);
}
if (low[now] == dfn[now]){
++scc_cnt;
do {
int now = stack[top--];
in_stack[now] = false;
sccno[now] = scc_cnt;
scc[scc_cnt].push_back(now);
whr[now] = scc[scc_cnt].size();
} while (stack[top + 1] != now);
}
}
signed main() {
scanf("%d%d%d%d", &n, &m, &S, &T);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
xym.add(u, v);
yzh.add(v, u);
++du[u];
U[i] = u, V[i] = v;
}
if (!check()) return puts("INF"), 0;
tarjan(S);
for (int i = 1; i <= scc_cnt; ++i) {
int siz = scc[i].size();
for (int j = 1; j <= siz; ++j) {
memset(key[j], 0x00, sizeof (double) * (siz + 1));
key[j][j] = 1;
val[j] = scc[i][j - 1] != T;
}
for (const auto& u: scc[i]) if (u != T)
for (int _ = xym.head[u], v; v = xym[_].to, _; _ = xym[_].nxt) {
if (sccno[u] == sccno[v]) {
key[whr[u]][whr[v]] -= 1.0 / du[u];
} else {
val[whr[u]] += 1.0 / du[u] * res[sccno[v]][whr[v]];
}
}
guass<110, 110>(siz, siz, key, val, res[i]);
}
printf("%.3lf", res[sccno[S]][whr[S]]);
return 0;
}
template <size_t N, size_t M>
int guass(int n, int m, double key[M][N], double val[M], double res[N]) {
if (m < n) return -1;
int nline = 1;
for (int i = 1; i <= n; ++i) {
int whr = nline;
for (int j = nline + 1; j <= m; ++j)
if (abs(key[j][i]) > abs(key[whr][i]))
whr = j;
if (abs(key[whr][i]) < eps) continue;
swap(val[nline], val[whr]);
for (int j = 1; j <= n; ++j) swap(key[nline][j], key[whr][j]);
for (int j = 1; j <= m; ++j) if (j != nline) {
double K = key[j][i] / key[nline][i];
val[j] -= K * val[nline];
for (int k = i; k <= n; ++k)
key[j][k] -= key[nline][k] * K;
}
++nline;
}
if (nline == n + 1) {
for (int i = 1; i <= n; ++i)
res[i] = val[i] / key[i][i] + eps;
return 0;
}
for (int i = nline; i <= m; ++i)
if (abs(val[i]) > eps)
return -2;
for (int i = 1; i <= nline; ++i)
res[i] = val[i] / key[i][i] + eps;
return -1;
}
[SDOI2012] 走迷宫 题解的更多相关文章
- 【BZOJ2707】[SDOI2012]走迷宫 Tarjan+拓扑排序+高斯消元+期望
[BZOJ2707][SDOI2012]走迷宫 Description Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,M ...
- BZOJ 2707: [SDOI2012]走迷宫( tarjan + 高斯消元 )
数据范围太大不能直接高斯消元, tarjan缩点然后按拓扑逆序对每个强连通分量高斯消元就可以了. E(u) = 1 + Σ E(v) / degree(u) 对拍时发现网上2个程序的INF判断和我不一 ...
- BZOJ 2707: [SDOI2012]走迷宫 [高斯消元 scc缩点]
2707: [SDOI2012]走迷宫 题意:求s走到t期望步数,\(n \le 10^4\),保证\(|SCC| \le 100\) 求scc缩点,每个scc高斯消元,scc之间直接DP 注意每次清 ...
- SDOI2012 走迷宫
走迷宫 Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发 ...
- 洛谷P1238 走迷宫题解
题目描述 有一个m*n格的迷宫(表示有m行.n列),其中有可走的也有不可走的,如果用1表示可以走,0表示不可以走,文件读入这m*n个数据和起始点.结束点(起始点和结束点都是用两个数据来描述的,分别表示 ...
- BZOJ2707 [SDOI2012]走迷宫 【概率dp + tarjan + 高斯消元】
题目 Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发的 ...
- [SDOI2012]走迷宫 (强连通分量缩点,动态规划,高斯消元)
题面 Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发的 ...
- bzoj 2707 [SDOI2012]走迷宫(SCC+高斯消元)
Description Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿 ...
- 洛谷 P6030 - [SDOI2012]走迷宫(高斯消元+SCC 缩点)
题面传送门 之所以写个题解是因为题解区大部分题解的做法都有 bug(u1s1 周六上午在讨论区里连发两个 hack 的是我,由于我被禁言才让 ycx 代发的) 首先碰到这种期望题,我们套路地设 \(d ...
- bzoj2702[SDOI2012]走迷宫
题意:给你一个有向图,点数10000,边数1000000,SCC大小不超过100(按数据范围的写法只有第三部分数据满足这个条件,不过第二部分数据并没有出现大小大于100个点的SCC,我是用数组大小为1 ...
随机推荐
- 轻松实现H5页面下拉刷新:滑动触发、高度提示与数据刷新全攻略
前段时间在做小程序到H5的迁移,其中小程序中下拉刷新的功能引起了产品的注意.他说到,哎,我们迁移后的H5页面怎么没有下拉刷新,于是乎,我就急忙将这部分的内容给填上. 本来是计划使用成熟的组件库来实现, ...
- 卷积神经网络-AlexNet
AlexNet 一些前置知识 top-1 和top-5错误率 top-1错误率指的是在最后的n哥预测结果中,只有预测概率最大对应的类别是正确答案才算预测正确. top-5错误率指的是在最后的n个预测结 ...
- mysql数据迁移-8.0.25
本文只简单描述一些逻辑迁移的问题,而且主要是针对开发过程中,小批量数据(例如100m之下的). 这几天装了个新的mysql8.0.25 64bit windows版本的. -- 看的出来oracle公 ...
- 07-Linux文件权限管理
文件的类型 Linux的哲学思想:一切皆文件. Linux的文件分为多种类型. 可以通过ll命令查看文件的类型: ll #输出: -rw-------. 1 root root 1266 2月 29 ...
- Linux 修改 hostname
背景 之前安装Linux系统的时候,没有明确指定.现在因为在做某些实验的时候,为了更好地区分我所登录的每一台服务器. 于是有了此文. 做法 首先修改/etc/hostname,修改为自己想要的名字xx ...
- ZYNQ:Linux添加I2C-RTC驱动
硬件情况 使用的是DS1338这款RTC时钟芯片,I2C总线对应到PS端的I2C1. 配置 内核 添加有关的驱动: 因为DS1338用的驱动与DS13307相似,一找发现是同一个配置. CONFIG_ ...
- [ABC184F] Programming Contest题解
前置知识 meet in middle (折半搜索) 会的大佬请跳过 不会的请自己前往oi wiki或CSDN(百度吧,少年) 解题思路 纯暴力 看完题目考虑将每一种情况计算出来,排序后找不超过T的最 ...
- Unity中自定义应用程序打开Assets目录下指定类型的文件
在Unity使用VS2017打开unityShader文件时总提示错误: 我也一直没找啥原因,shader文件直接使用VSCode打开,当然其他类型的文件也可这样处理用相应的exe打开,如:pdf,t ...
- PHP中substr() mb_substr() mb_struct()的区别和用法
PHP substr() 函数可以分割文字,但要分割的文字如果包括中文字符往往会遇到问题,这时可以用mb_substr()/mb_strcut这个函 数,mb_substr() /mb_strcut的 ...
- 基于django(爱抚宠物) 小程序设计和实现(源码+LW+部署讲解)
感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用 ...