[AGC009E Eternal Average] [建模 K进制 同余 简单DP]

先把问题转化成更形象的版本:你可以任意建一棵k叉树,并且需要满足恰有n+m叶子,其中有n个权值为1(黑点),m个权值为0(白点),其余节点的权值等于儿子节点的均值。问根节点有多少种可能的取值。

这棵树形象地模拟了合并的过程,并且我们可以看到:对于每一个黑色叶子结点i,记其深度为dep[i],那么它对根的贡献就是k^{-dep[i]}。由于这些贡献都是k的幂的形式,所以这启发我们在k进制下去想问题。我们安排一下每个黑点的深度,这就形成了一个k进制小数x_1={_{}}0.e_{1}e_{2}...e_{l},其中l<=\frac{n+m-1}{k-1}e_l\neq 0。现在问题就变成了:有多少个k进制小数x_1,满足它能够对应一棵合法的“合并树”。

假设所有叶子结点都是黑色,那么最后根节点权值必为1,我们假设白色的节点权值也是1,它们的贡献之和是k进制小数x_0,则有x_0+x_1=1,因此x_1合法的必要条件是存在x_0,使得x_0+x_1=1。反过来看就不难发现,这个条件也是充分的,因为将黑白叶子结点按照深度从大到小排序后,最深的点数一定是k的倍数(否则最后x_0+x_1不可能是整数),那我们将它们合并起来,就得到了若干深度-1的点,这样重复进行下去,一定可以合并成1个点,这棵树就构造好了。

现在已经知道了x_1能够对应一棵树的充要条件,但我们还有一个头疼的问题就是如何求x_1的个数。首先,我们的x_1并不是随随便便的一个k进制数,它必须能够被n个形如k^{-dep[i]}的数凑出才行,也就是k进制下给n个位置加1得到。同理,x_0必须由k进制下给m个位置加1得到。我们讨论一下x_1的约束(x_0同理):

一个最显然的约束就是\sum e_{i}<=n(约束1),因为就算不考虑进位,也最多只能贡献n个1。

但进位的话就复杂了。仔细观察,我们看到,一次进位相当于将k个1变成了更高位的一个1,也就是1的个数减少了(k-1),所以最终一定满足\sum e_i\equiv n\left ( mod (k-1)) \right(约束2),反过来,满足了这个式子,我们显然可以找到构造x_1的方案。

从下图可以看到,记x0的每一位为p_{i},则i<l时有p_i=(k-1)-e_i ,i=l时有p_i=k-e_i。为了方便,我们钦定最后一位已经放了一个1,这样m--,然后最后一位就也是p_l=(k-1)-e_l了。

综上,我们的x_1合法的充要条件是:

\sum e_{i}<=n

\sum e_i\equiv n\left ( mod (k-1)) \right

\sum (k-1)-e_{i}<=m

\sum (k-1)-e_i\equiv m-1\left ( mod (k-1)) \right

我们最后显然只关心sum=\sum e_{i}l,因此用g[l][sum]表示状态,进行一个简单DP即可统计出所有合法的x_1个数。复杂度O(\frac{n+m}{K-1}nK),已经近似O(N^2)了,但是我们其实也可以加个前缀和优化转移,这样更快:复杂度O(\frac{N^2}{K})

说了那么多,有点啰嗦了,但是代码短到不可思议:

#include <cstdio>
#include <algorithm>
#define ll long long
#define rep(i,j,k) for (i=j;i<=k;i++)
using namespace std;
const int N=2e3+5,mod=1e9+7;
int n,m,l,k,i,j,p,q,ans,g[2][N],s[N];
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	l=(n+m-1)/(k-1);
	g[0][0]=1; p=0; q=1;
	rep(i,1,l)
	{
		swap(p,q);
		s[0]=g[q][0];
		rep(j,1,n) s[j]=(g[q][j]+s[j-1])%mod;
		rep(j,0,min(n,k-1)) g[p][j]=s[j];
		rep(j,k,n) g[p][j]=(s[j]-s[j-k]+mod)%mod;
		rep(j,1,n)
			if ( j%(k-1)==n%(k-1) && (k-1-j%(k-1))%(k-1)==(m-1)%(k-1) && (k-1)*i-j<=m-1)
			  ans=((ll)ans+mod+g[p][j]+s[j-1]-s[j])%mod;
	}
	printf("%d\n",ans);
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值