关于高斯消元做有后效性dp的连边(待研究

这东西真的恶心。
CF113D Museum
来看注释。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
#define LL long long
#define uint unsigned int
using namespace std;
#define Debug(x) cerr << #x << ' ' << x
#define hh cerr << endl
const int MAXN = 25, MAXM = 505;
const double eps = 1e-9;
double b[MAXM][MAXM], D;
int n, m, N, d[MAXN];
double p[MAXN];
vector <int> v[MAXN];
// 迭代可以做 
// 有后效性dp,考虑高斯消元
inline int Getnum(int x, int y) { return (x - 1) * n + y; }
inline double Fabs(double x) { return x > 0 ? x : -x; }
void Gauss() {
	int r, c;
	for(r = 1, c = 1; r <= N && c <= N; r ++, c ++) {
		if(Fabs(b[r][c]) < eps) { printf("err %d|", r); return; }
		double k = b[r][c];
		for(int i = c; i <= N && i <= c + D; i ++) b[r][i] /= k;
		b[r][N + 1] /= k;
		for(int i = r + 1; i <= r + D && i <= N; i ++) {
			k = b[i][c] / b[r][c];
			for(int j = c; j <= N && j <= c + D; j ++) b[i][j] -= k * b[r][j];
			b[i][N + 1] -= k * b[r][N + 1];
		}
	}
}
void Check() {
	for(int i = N; i >= 1; i --) {
		for(int j = i + 1; j <= i + D && j <= N; j ++) {
			b[i][N + 1] -= b[j][N + 1] * b[i][j];
		}
	}
}
// dp[i][j] 表示经过 (i,j) 点的概率 
// 由于必须逆推(?),钦定一个终点当成起点跑 
// 注意钦定了起点为是 (x,x) , (y,y)就不被认为是合法的。这样是 n^7 的
// 但是,考虑刚才起点有多个,终点只有一个,需要试图将他们调换做到单源 
// 发现根据定义,逆推列出式子后,可以直接设初始值为 dp[s][t]=1
// 探究原因:这其实是一组恒等式,已经没有了方向性,所以从那边开始都可以
// 还是要考虑不能由 (x,x) 转移过来 
// 还有一个问题。。刚才是逆推,是已经到了 (x,x) 求 P(s,t),考虑起点 (x,x),不能由任何转移来,所以直接令 p=1 
// 现在是正推,(s,t) 可以由其它转移过来,所以不能直接令p=1 

// 好吧上面那种做法被 hack 了,原因很简单(其实一点也不简单),初始值设的 dp[s][t],转移却是反着转移
// 这就告诉我们初始值与转移的方向应该相同,尽管这是环形 dp 
// 看了一下题解的代码,发现正着推虽然转移到此点的概率加起来不为 1但也是可以的,原来之前一直想错了? 
// 这种题的难点:初末状态与dp式子的定义 
// It's difficult to slove. Maybe the reason is that I'm so vegetable.
int main() {
	int x, y, s, t; scanf("%d%d%d%d", &n, &m, &s, &t);
	for(int i = 1; i <= m; i ++) {
		scanf("%d%d", &x, &y); v[x].push_back(y); v[y].push_back(x); d[x] ++; d[y] ++;
	}
	for(int i = 1; i <= n; i ++) scanf("%lf", &p[i]); N = n * n;
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j <= n; j ++) {
			if(i == s && j == t) b[Getnum(s, t)][N + 1] = 1;
			b[Getnum(i, j)][Getnum(i, j)] = 1;
			for(int U = 0; U <= v[i].size(); U ++) {
				for(int V = 0; V <= v[j].size(); V ++) {
					int q, w;
					double gl1, gl2;
					if(U < v[i].size()) q = v[i][U], gl1 = (1 - p[q]) / d[q];
					else q = i, gl1 = p[q];
					if(V < v[j].size()) w = v[j][V], gl2 = (1 - p[w]) / d[w];
					else w = j, gl2 = p[w];
					if(q == w) continue;
		//			printf("|%d %d -> %d %d %lf %lf|\n", i, j, q, w, gl1, gl2);
					b[Getnum(i, j)][Getnum(q, w)] -= gl1 * gl2;
				}
			}
		}
	}
//		for(int i = 1; i <= N; i ++) {
//			for(int j = 1; j <= N + 1; j ++) printf("%.2lf ", b[i][j]);
//			printf("\n");
//		}
//		printf("\n");
	D = N; Gauss(); Check();
	for(int i = 1; i <= n; i ++) printf("%.7f ", b[Getnum(i, i)][N + 1]);
	return 0;
}

至少现在我可以得出的结论:
1.初始值的设置要与转移方向一致,也就是说,不能最后转移来转移去转移到了一个赋了的点上去,哪怕它是环形 dp。
2.令 \(S\) 为终点(可能有多个),则不能由 \(S->T\)。听起来很扯对吧,对于本题,就是建初始矩阵时那一句“if(q == w) continue”。
3.要求具体的某一个的期望时不能逆推(即切换起点、终点)。求起点到终点的期望时可以逆推。
4.我突然悟道:根本就没有什么逆推正推,这取决于结束点在那里。

posted @ 2021-10-16 16:26  Saintex  阅读(53)  评论(0编辑  收藏  举报