1. 程式人生 > 其它 >Codeforces 1629F. Game on Sum

Codeforces 1629F. Game on Sum

傳送門

Easy Version

\(\texttt{Difficulty:2100}\)

題目大意

\(\texttt{Alice}\)\(\texttt{Bob}\) 進行一個遊戲,遊戲初始分數 \(x=0\) ,每輪 \(\texttt{Alice}\) 先指定一個實數 \(t\in[0,k](0\le k\le10^9+7)\) ,之後 \(\texttt{Bob}\) 可以選擇令 \(x=x+t\) 或是 \(x=x-t\)\(\texttt{Bob}\)\(x=x+t\) 的次數至少為 \(m(1\le m\le2000)\) ,遊戲總共進行 \(n(1\le n\le2000)\) 輪,\(\texttt{Alice}\)

想讓 \(x\) 儘可能大, \(\texttt{Bob}\) 想讓 \(x\) 儘可能小,在雙方都採用最優策略的情況下,求最後的 \(x\)

思路

考慮 \(dp\) ,設 \(f_{i,j}\) 為遊戲進行了 \(i\) 輪, \(\texttt{Bob}\) 執行了 \(j\) 次加法操作時, \(x\) 的值。
顯然 \(f_{i,j}\) 可以由 \(f_{i-1,j}\)\(f_{i-1,j-1}\) 轉移而來,分別可以得到 \(f_{i-1,j}-t\)\(f_{i-1,j-1}+t\) 。顯然對於所有 \(t\) 的取值,兩個結果分別遞增和遞減, 而 \(\texttt{Bob}\)

一定會選擇二者中答案更小的來作為轉移的結果 ,於是 \(\texttt{Alice}\) 選擇的 \(t\) 需要使得兩個結果相等,也就是 \(t=\frac{f_{i-1, j}-f_{i-1,j-1}}{2}\) ,於是 \(f_{i,j}=\frac{f_{i-1, j}+f_{i-1,j-1}}{2}\) 。初始值為 \(f_{i,i}=ik\) , 其餘為 \(0\) 。最後 \(f_{n,m}\) 即為答案,複雜度 \(O(n^2)\)

程式碼

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mk make_pair
//#define int LL
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 2010;

LL T, N, M, K, f[maxn][maxn], two = 500000004;

void solve()
{
	for (int i = 1; i <= N; i++)
		f[i][i] = i * K % MOD;
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j < i; j++)
			f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % MOD * two % MOD;
	}
	cout << f[N][M] << endl;
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		cin >> N >> M >> K;
		for (int i = 1; i <= N; i++)
		{
			for (int j = 1; j <= M; j++)
				f[i][j] = 0;
		}
		solve();
	}

	return 0;
}

Hard Version

\(\texttt{Difficulty:2400}\)
資料範圍變為 \((1\le n,m\le10^6)\)

思路

可以發現在 \(\texttt{Easy Version}\) 進行的 \(dp\) 中,所有的答案都是由 \(f_{i,i}\) 推出,我們考慮對於 \(f_{n,m}\) ,每個 \(f_{i,i}\) 對於答案的貢獻是怎樣的,發現每個 \(f_{i,i}\) 的計算次數就是 \(f_{i+1,i}\) 的計算次數,而對於 \(f_{i,j}(i>j)\) ,其每次可以向 \(f_{i+1,j}\)\(f_{i+1,j+1}\) 轉移,也就是向正下方和右下方轉移,其計算次數就是轉移到 \(f_{n,m}\) 的總路徑數,即 \(\binom{n-i}{m-j}\) ,顯然只有 \(i\le m\) 時的 \(f_{i,i}\) 才會對答案產生貢獻。 此外對於每次轉移,原來的值還要除以 \(2\) ,於是可以得出 \(f_{n,m}=\sum_{i=1}^m \frac{ik\binom{n-i-1}{m-i}}{2^{n-i}}\) 。直接計算即可,此外在 \(n=m\) 時需要特判答案為 \(nk\) ,複雜度 \(O(n)\)

程式碼

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
using PII = pair<int, int>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mk make_pair
//#define int LL
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 1000010;

LL T, N, M, K, two[maxn], fact[maxn], invfact[maxn], inv[maxn], B = 500000004;

LL qpow(LL a, LL x, LL m)
{
	LL ans = 1;
	while (x)
	{
		if (x & 1)
			ans = ans * a % m;
		x >>= 1;
		a = a * a % m;
	}

	return ans % m;
}

void inv_init(LL n, LL m)
{
	inv[1] = 1;
	for (LL i = 2; i <= n; i++)
	{
		LL j = m % i;
		inv[i] = (-inv[j] * (m / i) % m + m) % m;
	}
}

void fact_init(LL n, LL m)
{
	fact[0] = fact[1] = 1;
	for (LL i = 2; i <= n; i++)
		fact[i] = fact[i - 1] * i % m;
	invfact[n] = qpow(fact[n], m - 2, m);
	for (LL i = n; i > 0; i--)
		invfact[i - 1] = invfact[i] * i % m;
}

LL C(LL x, LL y)
{
	if (x < 0 || y < 0 || x - y < 0)
		return 0;
	LL ans = 1;
	for (LL i = 0; i < y; i++)
		ans = ans * (x - i) % MOD;
	ans = ans * invfact[y] % MOD;

	return ans;
}

void solve()
{
	LL ans = 0;
	if (N == M)
		ans = N * K % MOD;
	else
	{
		LL tmp = C(N - 2, M - 1);
		for (LL i = 1; i <= M; i++)
		{
			ans = (ans + tmp * i % MOD * K % MOD * two[N - i] % MOD) % MOD;
			tmp = tmp * (M - i) % MOD * inv[N - i - 1] % MOD;
		}
	}
	cout << ans << endl;
}

int main()
{
	IOS;
	fact_init(1000000, MOD), inv_init(1000000, MOD);
	two[0] = 1, two[1] = B;
	for (int i = 2; i <= 1000000; i++)
		two[i] = two[i - 1] * B % MOD;
	cin >> T;
	while (T--)
	{
		cin >> N >> M >> K;
		solve();
	}

	return 0;
}