【题解】CF1628D2 Game on Sum (Hard Version)

题目传送门

思路

这是一道DP题。

所以我们来设状态,我们让 \(dp_{i,j}\) 表示在第 i 次操作后, Bob 选择“加”了 j 次,那么显然最后的答案是 \(dp_{n,m}\)

再来看转移:

\(dp_{i,j}\) 的 i 一定由 i-1 转移而来,而 j 则取决于 Bob 加不加,所以可以贡献给它的状态是 \(dp_{i-1,j}\)\(dp_{i-1,j-1}\)

反过来说, \(dp_{i,j}\) 可以给 \(dp_{i+1,j}\)\(dp_{i+1,j+1}\) 作出贡献。

先考虑转移。

显然,Alice 作出决策后, Bob 会在 \(dp_{i-1,j}-t\)\(dp_{i-1,j-1}+t\) 中选择最小的一个。

所以,Alice显然要让它们相等,否则Alice就会亏一波。

也就是,她会让 这个状态的 x \(=\frac{dp_{i-1,j}\ \ \ +\ dp_{i-1,j-1}}{2}\),因为只有这样才可以保证 \(dp_{i-1,j}-t\)\(dp_{i-1,j-1}+t\) 中没有最小值,Alice才不会亏。

边界为 \(dp_{i,0}=0\)(一次都不加,Alice当然全选 0 )和 \(dp_{i,i}=ik\) (全加,Alice当然要拉满)。

看起来,接下来是一个 \(\Theta(nm)\) 的 dp。

不过,我们发现,它的转移很像一个杨辉三角!

所以,我们考虑 \(dp_{i,i}\)\(dp_{n,m}\) 的贡献。(因为在填充之前只有它们可以对答案造成贡献)

\(dp_{i,j}\) 只能给 \(dp_{i+1,j}\)\(dp_{i+1,j+1}\) 作出贡献。

所以,它需要连续将 i 进行一些 +1 ,并选择一些来把 j 也来加够。

然而,如果直接分析, \(dp_{i,i}\) 会给 \(dp_{i+1,i+1}\) 贡献,然而这里已经填好了,所以会挂。

注意到 \(dp_{i,j}\) 不能贡献给 \(dp_{i,j+1}\) ,我们考虑从 \(dp_{i+1,i}\) 开始计算答案。

这时,向 n 需要加 \(n-i-1\) ,向 m 需要加 \(m-i\) ,而这 \(m-i\) 一共有 \(\tbinom{n-i-1}{m-i}\) 种方案,需要乘上。

而在这个过程中,它显然会被不断除2,一共会除 \(n-i\) 次,因为我们直接从 \(dp_{i+1,i}\) 开始转移就意味着已经进行了一次操作。

显然如果 m 超过规定范围 Bob 就亏了,所以考虑用 m 来限制枚举变量。

最后的答案,就是 \(\sum\limits^m_{i=1}\tfrac{ik\ \cdot\ \tbinom{n-i-1}{m-i}}{2^{n-i}}\)

当然,如果 \(n=m\) ,前面也说过, \(dp_{i,i}\) 不应当再给它作贡献,此时的答案是 \(km\) ,应当特判。

预处理 \(2^i\) 的逆元,复杂度 \(\Theta(\sum n)\)

代码

//吾日八省吾身:
//输入多而不快读乎?
//题目标注而不freopen乎?
//乘除并列先乘后除乎?
//不手撕样例直接写代码乎?
//不仔细读题直接关页面乎?
//1e9而不开long long乎?
//Ctrl+V而不改名称乎?(papaw->papan IMPLIES tg1=->2=)
//相信评测神机乎?
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime>
#include<sstream>
#include<list>
#include<bitset>
using namespace std;
typedef long long ll;
const ll MO(1000000007);
const ll MAXN(1000123);
ll fac[MAXN],inv[MAXN];
ll tpow[MAXN];
ll T,n,m,k,ans;
void R(ll &x){
	x=0;ll f=1;char c='c';
	while(c>'9'||c<'0'){f=f*(c=='-'?-1:1);c=getchar();}
	while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
	x=x*f;
	return;
} 
ll fpow(ll x,ll y){
	ll res=1;
	while(y){if(y&1) res=(res*x)%MO;x=(x*x)%MO;y>>=1;}
	return res%MO;
}
void getfi(){
	const ll MAXA=1000100;
	fac[0]=inv[0]=tpow[0]=1;
	for(int i=1;i<=MAXA;++i) fac[i]=(fac[i-1]*i)%MO,tpow[i]=(tpow[i-1]<<1)%MO;
	inv[MAXA]=fpow(fac[MAXA],MO-2);tpow[MAXA]=fpow(tpow[MAXA],MO-2);
	for(int i=MAXA-1;i>=1;--i) inv[i]=(inv[i+1]*(i+1))%MO,tpow[i]=(tpow[i+1]<<1)%MO;
	return;
}
ll C(ll n,ll m){return (fac[n]*inv[m]%MO*inv[n-m]%MO);}
int main(){
	getfi();
	R(T);
	while(T--){
		R(n);R(m);R(k);ans=0;
		if(n==m){printf("%lld\n",n*k%MO);continue;}
		for(int i=1;i<=m;++i) ans=(ans+i*k%MO*C(n-i-1,m-i)%MO*tpow[n-i]%MO)%MO;
		printf("%lld\n",ans);
	}
	return 0;
}

P.S.

温馨提示:可以到 D1 版本水双倍经验(bushi)

posted @ 2022-04-09 20:47  Binaries  阅读(51)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end