HDU6656 Kejin Player - 动态规划DP - 数学期望

Kejin Player

Time Limit: 10000/5000 MS (Java/Others)   Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 342   Accepted Submission(s): 115

Problem Description

Cuber QQ always envies those Kejin players, who pay a lot of RMB to get a higher level in the game. So he worked so hard that you are now the game designer of this game. He decided to annoy these Kejin players a little bit, and give them the lesson that RMB does not always work.

This game follows a traditional Kejin rule of “when you are level i, you have to pay ai RMB to get to level i+1”. Cuber QQ now changed it a little bit: “when you are level i, you pay ai RMB, are you get to level i+1 with probability pi; otherwise you will turn into level xi (xi≤i)”.

Cuber QQ still needs to know how much money expected the Kejin players needs to ``ke’’ so that they can upgrade from level l to level r, because you worry if this is too high, these players might just quit and never return again.

Input

The first line of the input is an integer t, denoting the number of test cases.

For each test case, there is two space-separated integers n (1≤n≤500 000) and q (1≤q≤500 000) in the first line, meaning the total number of levels and the number of queries.

Then follows n lines, each containing integers ri, si, xi, ai (1≤ri≤si≤109, 1≤xi≤i, 0≤ai≤109), space separated. Note that pi is given in the form of a fraction risi.

The next q lines are q queries. Each of these queries are two space-separated integers l and r (1≤l<r≤n+1).

The sum of n and sum of q from all t test cases both does not exceed 106.

Output

For each query, output answer in the fraction form modulo 109+7, that is, if the answer is PQ, you should output P⋅Q−1 modulo 109+7, where Q−1 denotes the multiplicative inverse of Q modulo 109+7.

Sample Input

1
3 2
1 1 1 2
1 2 1 3
1 3 3 4
1 4
3 4

Sample Output

22
12

 
 

题目大概意思:

n + 1 ( 1 n 5 × 1 0 5 ) n+1(1≤n≤5×10^5) 个节点,当处于第 i ( 1 i n ) i(1≤i≤n) 个节点时,在花费 a i ( 0 a i 1 0 9 ) a_i(0≤a_i≤10^9) 金额后,有 p i ( 0 &lt; p i 1 0 9 ) p_i(0&lt;p_i≤10^9) 的概率移动至第 i + 1 i+1 个节点,否则移动至第 x i ( 1 x i i ) x_i(1≤x_i≤i) 个节点。进行 Q Q 次询问,每次询问包含 2 2 个整数 l i , r i ( 1 l i &lt; r i n + 1 ) l_i,r_i(1≤l_i&lt;r_i≤n+1) ,输出从节点 l i l_i 移动至节点 r i r_i 的花费的期望值。其中 p i p_i 由分数形式给出, p i = r i s i p_i=\frac{r_i}{s_i} ,并将结果表示为 m o d &ThinSpace;&ThinSpace; ( 1 0 9 + 7 ) \mod{(10^9+7)} 下的分数形式,即将结果 P Q \frac{P}{Q} 表示为 P Q 1 m o d &ThinSpace;&ThinSpace; ( 1 0 9 + 7 ) P·Q^{-1}\mod{(10^9+7)} ,其中 Q 1 Q^{-1} 是在 m o d &ThinSpace;&ThinSpace; ( 1 0 9 + 7 ) \mod{(10^9+7)} 下的乘法逆元。

 
 

分析:

由于每次只能向前移动 1 1 步,因此想要移动至节点 m m ,则必须遍历 m m 之前的所有节点一步一步移动至 m m ,而不可能从 m m 之后的节点跳回 m m 而到达,因此区间内花费的期望值满足可加减性,即从节点 l i l_i 移动至节点 r i r_i 的花费的期望就等于从节点 1 1 移动至 r i r_i 的花费 c r c_r 减去从节点 1 1 移动至节点 l i l_i 的花费 c l c_l ,因此只需求出从节点 1 1 移动至每个节点的花费即可。设:

d p [ i ] = &ThinSpace; 1 &ThinSpace; &ThinSpace; i &ThinSpace; dp[i]= 从节点\,1\,移动至节点\,i\,的花费的期望值

则有如下方程:

d p [ 1 ] = 0 ( d p [ i + 1 ] d p [ i ] ) = d p [ i ] + p i a i + ( 1 p i ) ( d p [ i ] d p [ x i ] + a i + ( d p [ i + 1 ] d p [ i ] ) ) \begin{aligned} dp&amp;[1]=0\\ (dp&amp;[i+1]-dp[i])=dp[i]+p_i·a_i+(1-p_i)·(dp[i]-dp[x_i]+a_i+(dp[i+1]-dp[i])) \end{aligned}

解得 d p [ i + 1 ] = d p [ i ] + a i + ( 1 p i ) ( d p [ i ] d p [ x i ] + a i ) p i dp[i+1]=dp[i]+a_i+\frac{(1-p_i)·(dp[i]-dp[x_i]+a_i)}{p_i}

因此只需 O ( n ) O(n) 次状态转移即可得到从节点 1 1 移动至任意节点的花费的期望。

由于题目要求表示为分数形式,因此参与运算的实数需要用分子 p p 与分母 q q 两个变量表示。为了减少求解逆元的次数,还可以额外使用一个变量存储分母的逆元 q i n v q_{inv} ,这样,在初始化一个实数时求解一次逆元后,利用 ( A B ) 1 = A 1 B 1 (A·B)^{-1}=A^{-1}·B^{-1} 这一性质,在进行加法、减法、乘法运算时就不再需要求解逆元了,而在进行除法运算时只需要求解一次逆元。

整个算法中共进行了 O ( n ) O(n) 次状态转移,每次状态转移时除了常数次基本运算外,还需要求解一次逆元,而求解逆元的时间复杂度不超过 O ( log n ) O(\log{n}) ,因此算法的总时间复杂度为 O ( n log n ) O(n·\log{n}) .

 
 
下面贴代码:

#include <cstdio>
using namespace std;

typedef long long ll;

const ll MOD = (ll)1e9 + 7;
const int MAX_N = 500020;

ll mod_inv(ll a, const ll& m = MOD);
ll exgcd(ll a, ll b, ll& x, ll& y);

// 表示实数的结构体
struct P
{
	ll p;
	ll q;
	ll q_inv;

	P() :p(0), q(1), q_inv(1) {}
	P(const ll& p, const ll& q, const ll& q_inv)
		:p(p), q(q), q_inv(q_inv) {}

	ll value()const
	{
		return p * q_inv % MOD;
	}

	P& operator+= (const P& ri)
	{
		this->p = (p * ri.q + ri.p * q) % MOD;
		this->q = this->q * ri.q % MOD;
		this->q_inv = this->q_inv * ri.q_inv % MOD;
		return *this;
	}
	P& operator-= (const P& ri)
	{
		this->p = ((p * ri.q - ri.p * q) % MOD + MOD) % MOD;
		this->q = this->q * ri.q % MOD;
		this->q_inv = this->q_inv * ri.q_inv % MOD;
		return *this;
	}
	P& operator*= (const P& ri)
	{
		this->p = this->p * ri.p % MOD;
		this->q = this->q * ri.q % MOD;
		this->q_inv = this->q_inv * ri.q_inv % MOD;
		return *this;
	}
	P& operator/= (const P& ri)
	{
		this->p = this->p * ri.q % MOD;
		this->q = this->q * ri.p % MOD;
		this->q_inv = mod_inv(q);
		return *this;
	}

	P operator+ (const P& ri)const
	{
		P tmp(*this);
		return tmp += ri;
	}
	P operator- (const P& ri)const
	{
		P tmp(*this);
		return tmp -= ri;
	}
	P operator* (const P& ri)const
	{
		P tmp(*this);
		return tmp *= ri;
	}
	P operator/ (const P& ri)const
	{
		P tmp(*this);
		return tmp /= ri;
	}
}const Eye(1, 1, 1), Zero;

P dp[MAX_N];


int main()
{
	int T, N, Q, l, r, s, x, a;
	scanf("%d", &T);

	while (T--)
	{
		scanf("%d%d", &N, &Q);
		for (int i = 1; i <= N; i++)
		{
			scanf("%d%d%d%d", &r, &s, &x, &a);
			P _a(a, 1, 1);
			P _p(r, s, mod_inv(s));
			dp[i + 1] = dp[i] + _a + (Eye - _p) * (dp[i] - dp[x] + _a) / _p;
		}

		while (Q--)
		{
			scanf("%d%d", &l, &r);
			printf("%d\n", (int)(dp[r] - dp[l]).value());
		}
	}
	return 0;
}

// 求解 a 在 mod(m) 下的逆元
ll mod_inv(ll a, const ll& m)
{
	ll x, y;
	exgcd(a, m, x, y);
	return (m + x % m) % m;
}

// 扩展欧几里德算法
ll exgcd(ll a, ll b, ll& x, ll& y)
{
	ll d = a;
	if (b)
	{
		d = exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
	else
	{
		x = 1;
		y = 0;
	}
	return d;
}

原创文章 42 获赞 22 访问量 3030

猜你喜欢

转载自blog.csdn.net/weixin_44327262/article/details/99334470