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(1n5×105)n+1(1≤n≤5×10^5) 個節點,當處於第 i(1in)i(1≤i≤n) 個節點時,在花費 ai(0ai109)a_i(0≤a_i≤10^9) 金額後,有 pi(0&lt;pi109)p_i(0&lt;p_i≤10^9) 的概率移動至第 i+1i+1 個節點,否則移動至第 xi(1xii)x_i(1≤x_i≤i) 個節點。進行 QQ 次詢問,每次詢問包含 22 個整數 li,ri(1li&lt;rin+1)l_i,r_i(1≤l_i&lt;r_i≤n+1) ,輸出從節點 lil_i 移動至節點 rir_i 的花費的期望值。其中 pip_i 由分數形式給出, pi=risip_i=\frac{r_i}{s_i} ,並將結果表示爲mod&ThinSpace;&ThinSpace;(109+7)\mod{(10^9+7)} 下的分數形式,即將結果 PQ\frac{P}{Q} 表示爲 PQ1mod&ThinSpace;&ThinSpace;(109+7)P·Q^{-1}\mod{(10^9+7)} ,其中 Q1Q^{-1} 是在mod&ThinSpace;&ThinSpace;(109+7)\mod{(10^9+7)} 下的乘法逆元。

 
 

分析:

由於每次只能向前移動 11 步,因此想要移動至節點 mm ,則必須遍歷 mm 之前的所有節點一步一步移動至 mm ,而不可能從 mm 之後的節點跳回 mm 而到達,因此區間內花費的期望值滿足可加減性,即從節點 lil_i 移動至節點 rir_i 的花費的期望就等於從節點 11 移動至 rir_i 的花費 crc_r 減去從節點 11 移動至節點 lil_i 的花費 clc_l ,因此只需求出從節點 11 移動至每個節點的花費即可。設:

dp[i]=&ThinSpace;1&ThinSpace;&ThinSpace;i&ThinSpace;dp[i]= 從節點\,1\,移動至節點\,i\,的花費的期望值

則有如下方程:

dp[1]=0(dp[i+1]dp[i])=dp[i]+piai+(1pi)(dp[i]dp[xi]+ai+(dp[i+1]dp[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}

解得dp[i+1]=dp[i]+ai+(1pi)(dp[i]dp[xi]+ai)pidp[i+1]=dp[i]+a_i+\frac{(1-p_i)·(dp[i]-dp[x_i]+a_i)}{p_i}

因此只需 O(n)O(n) 次狀態轉移即可得到從節點 11 移動至任意節點的花費的期望。

由於題目要求表示爲分數形式,因此參與運算的實數需要用分子 pp 與分母 qq 兩個變量表示。爲了減少求解逆元的次數,還可以額外使用一個變量存儲分母的逆元 qinvq_{inv} ,這樣,在初始化一個實數時求解一次逆元后,利用 (AB)1=A1B1(A·B)^{-1}=A^{-1}·B^{-1} 這一性質,在進行加法、減法、乘法運算時就不再需要求解逆元了,而在進行除法運算時只需要求解一次逆元。

整個算法中共進行了 O(n)O(n) 次狀態轉移,每次狀態轉移時除了常數次基本運算外,還需要求解一次逆元,而求解逆元的時間複雜度不超過 O(logn)O(\log{n}) ,因此算法的總時間複雜度爲 O(nlogn)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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章