BJOI2019 光线

落谷

Solution

考虑一条光线最终的作用效果只有两种:要么向下打入玻璃,要么向上打入玻璃。而我们要求的是向下打入第 \(n\) 层玻璃的量。

所以对于每种作用方式建立一个未知数,或者也可以理解为一种广义的 DP:

  • \(f_i\) 为向下打入第 \(i\) 层玻璃的单位亮光
  • \(g_i\) 为向上打入第 \(i\) 层玻璃的单位亮光

如图,黑线是玻璃,黑线左边的数字是玻璃标号,蓝色线是第一种光线,红色光线是第二种光线。

方程之间的等式

显然,考虑对于每一种作用光线由什么光线转化而成的即可,注意这里转化的方式是反射、投射:

  • \(f_1 = 1\),为初始打入的光线。

  • \(f_i\ (2 \le i \le n) = a_{i - 1} \% ·f_{i - 1} + b_{i - 1} \% \cdot g_{i - 1}\)。(绿色箭头的转移方式)。

  • \(g_n = 0\)

  • \(g_{i}\ (1 \le i < n) = a_{i + 1}\% \cdot g_{i + 1} + b_{i + 1}\% \cdot f_{i + 1}\)。(橙色箭头的转移方式)。

这样,我们就构建好了一个具有 $2n$ 个未知数,$2n$ 个方程的方程组。

发现如果知道 \(f_{i}, g_{i}\),可以推出 \(f_{i + 1}, g_{i + 1}\),因为 \(f_1 = 0\),不妨设 \(g_1 = x\)。那么所有数都可以表示为 \(ax + b\) 的形式。推至最后令 \(g_n = 0\) 算出 \(x\),最后带入 \(x\) 算入相应的 \(f_n\)

#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;

const int N = 500005, P = 1e9 + 7;

int n, a[N], b[N], f[N][2], g[N][2];

int inline power(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (LL)res * a % P;
		a = (LL)a * a % P;
		b >>= 1;
	}
	return res;
}

int main() {
	scanf("%d", &n);
	int inv = power(100, P - 2);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", a + i, b + i);
		a[i] = (LL)a[i] * inv % P, b[i] = (LL)b[i] * inv % P;
	}
	f[1][0] = 1, g[1][1] = 1;
	for (int i = 2; i <= n; i++) {
		f[i][0] = ((LL)a[i - 1] * f[i - 1][0] + (LL)b[i - 1] * g[i - 1][0]) % P;
		f[i][1] = ((LL)a[i - 1] * f[i - 1][1] + (LL)b[i - 1] * g[i - 1][1]) % P;
		g[i][0] = (g[i - 1][0] - (LL)f[i][0] * b[i] % P + P) * power(a[i], P - 2) % P;
		g[i][1] = (g[i - 1][1] - (LL)f[i][1] * b[i] % P + P) * power(a[i], P - 2) % P;
	}
	int x = (LL)(P - g[n][0]) * power(g[n][1], P - 2) % P;
	printf("%lld\n", (f[n][0] + (LL)f[n][1] * x % P) * a[n] % P);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/dmoransky/p/12571615.html