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;
}