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;
}
posted @ 2022-05-18 16:39  Prgl  阅读(38)  评论(0)    收藏  举报