[NOI2005]聪聪与可可 解题报告
定位:一道期望+图论好题。
题目链接
题目大意
太长了没有。
但数据范围是 1 ≤ N , E ≤ 1000 1\le N,E\le 1000 1≤N,E≤1000
解题报告
我们发现不仅鼠在动,猫也在动,所以不妨设二维dp, f ( a , b ) f(a,b) f(a,b) 表示猫在 a a a ,老鼠在 b b b ,并且猫先走,猫抓住老鼠的期望步数。
设 n s ( a , b ) ns(a,b) ns(a,b) 表示猫在 a a a ,老鼠在 b b b ,猫下一步走到哪。于是有以下转移:
- 如果猫和老鼠重合,即 a = b a=b a=b ,则 f ( a , b ) = 0 f(a,b)=0 f(a,b)=0.
- 如果猫走一步或两步就能抓住鼠,那么鼠连走的机会都没有,那么只需一步。即若 n s ( a , b ) = b ns(a,b)=b ns(a,b)=b 或 n s ( n s ( a , b ) , b ) = b ns(ns(a,b),b)=b ns(ns(a,b),b)=b ,则 f ( a , b ) = 1 f(a,b)=1 f(a,b)=1
- 如果猫这一次还抓不住鼠,那么转移就变得复杂了。是下面这个式子:
f ( a , b ) = [ ∑ k f ( n s ( n s ( a , b ) , b ) , k ) ] / ( d e g [ b ] + 1 ) + 1 f(a,b)=[\sum_kf(ns(ns(a,b),b),k)]/(deg[b]+1) +1 f(a,b)=[k∑f(ns(ns(a,b),b),k)]/(deg[b]+1)+1
其中 k k k 是从 b b b 一部可跳到的点,包括 b b b 本身!
容易发现这个转移是没有后效性的。
那么关键在于,如何求出任意一对 a , b a,b a,b 的 n s ns ns 来呢?
由于 n s ns ns 一定在 a → b a\to b a→b 的最短路上,我们先求全局最短路,就可以通过枚举 a a a 可到达的顶点,看看它在不在最短路上,选个标号最小的就行。
感觉复杂度假了?我们先看代码,再做复杂度分析。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef long double ldb;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
ll x = 0, f = 1; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
return x * f;
}
const int MAXN = 1005;
const int MAXE = 1005;
int N, E;
int C, M;
int head[MAXN], ver[MAXE * 2], nxt[MAXE * 2], e_num, deg[MAXN];
void addedge(int u, int v) {
ver[++e_num] = v; nxt[e_num] = head[u]; head[u] = e_num;
}
int dis[MAXN][MAXN], vis[MAXN];
int que[MAXN], hd, tl;
void bfs(int s, int dis[]) {
memset(vis, 0x00, sizeof vis);
hd = 1, tl = 0;
que[++tl] = s;
vis[s] = 1; dis[s] = 0;
while(hd <= tl) {
int u = que[hd++];
for(int i = head[u]; i; i = nxt[i]) {
int v = ver[i]; if(vis[v]) continue;
vis[v] = 1;
dis[v] = dis[u] + 1;
que[++tl] = v;
}
}
}
int ns[MAXN][MAXN];
ldb f[MAXN][MAXN];
bool vis2[MAXN][MAXN];
ldb dfs(int a, int b) {//cat first
if(a == b) return 0;
if(ns[a][b] == b || ns[ns[a][b]][b] == b) return 1;
if(vis2[a][b]) return f[a][b];
vis2[a][b] = 1; f[a][b] = 0;
int nns = ns[ns[a][b]][b];
for(int i = head[b]; i; i = nxt[i]) {
int v = ver[i];
f[a][b] += dfs(nns, v);
}
f[a][b] += dfs(nns, b);
f[a][b] /= deg[b] + 1;
f[a][b]++;
return f[a][b];
}
int main() {
N = read(), E = read(), C = read(), M = read();
for(int i = 1; i <= E; i++) {
int u = read(), v = read();
addedge(u, v); addedge(v, u);
deg[u]++; deg[v]++;
}
for(int i = 1; i <= N; i++) {
bfs(i, dis[i]);
}
memset(ns, 0x3f, sizeof ns);
for(int i = 1; i <= N; i++)
for(int j = 1; j <= N; j++) if(i != j)
for(int k = head[i]; k; k = nxt[k]) {
int t = ver[k];
if(dis[i][j] == dis[t][j] + 1) ns[i][j] = min(ns[i][j], t);//t is in the shortest path of (i, j)
}
printf("%.3Lf\n", dfs(C, M));
return 0;
}
复杂度分析
首先一个大头就是全局最短路。由于边权都为1,我们可以跑 n n n 次bfs,复杂度 O ( n ( n + m ) ) O(n(n+m)) O(n(n+m)).
然后是求 n s ns ns 数组。枚举点是 O ( n 2 ) O(n^2) O(n2) 的,而每条边都会被枚举 O ( 1 ) O(1) O(1) 次,所以复杂度是 O ( n 2 + m ) O(n^2+m) O(n2+m)
然后是记忆化搜素,显然不超过 O ( n m ) O(nm) O(nm) .
所以最后还大概是 O ( n 2 ) O(n^2) O(n2) 的级别,没有假掉。
但如果你最短路跑 Dijkstra 或 SPFA 甚至 Floyd,那可能就没了。