【Codeforces】Pinely Round 1 (Div. 1 + Div. 2) D. Carry Bit | 组合数

75 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

【Codeforces】Pinely Round 1 (Div. 1 + Div. 2) D. Carry Bit | 组合数

题目链接

Problem - D - Codeforces

题目

image.png

题目大意

函数 g(x)g(x) 表示数字 xx 在二进制表示下 1 的个数。函数 f(x,y)=g(x)+g(y)g(x+y)f(x,y)=g(x)+g(y)-g(x+y)。对于给定的 nnkk,一个好的数对 (a,b)(a,b) 需要满足下述条件:

  • f(a,b)=kf(a,b)=k
  • 0a,b<2n0\le a,b< 2^n

对于给定的 nnkk,求有多少个好的数对。如果 (a1,b1)(a_1,b_1) 是好的数对,且 a1b1a_1\neq b_1,那么 (a1,b1)(a_1,b_1)(a2,b2)(a_2,b_2) 算两种方案。

思路

我们取出 aabb 的一个数位列出所有的可能如下表所示。

进位abg(进位)+g(a)+g(b)g(进位+a+b)
00000
00111
01011
01121
10011
10121
11021
11132

观察表格可以发现,如果当前位产生进位,就会对 ff 函数产生 1 点贡献。原题可以转化为在 nn 个位置里找到 kk 个位置产生了进位的方案数。

继续观察表格,如果前一位产生了进位,当前位置就有 3 种填数方案使得当前位置也产生进位,只有 1 种填数方案使得当前位置不产生进位;如果前一位没进位,当前位置就有 3 种填数方案使得当前位置也不进位,只有 1 种填数方案使得当前位置产生进位。

则如果我们把 kk 次进位切分成 ii 段,切分的方案是典型的球盒模型,可以用隔板法解决,为 Ck1i1C_{k-1}^{i-1}。从进位到不进位、不进位到进位的转移只有 1 种填数方案,相同状态间的转移则有 3 种填数方案,且相互独立。

我们需要用不进位段把 ii 段隔开,显然有如下图所示的四种情况:

image.png

  • A:划分出 i+1i+1 段不进位的位置,方案数为 Cnk1iC_{n-k-1}^{i}。其中有 2×k2\times k 次进位与进位之间的转换,即有 2×k2\times k 个位置填数方案唯一,其他 n2×kn-2\times k 个位置分别有 3 种填数方案。把 kk 次进位切分成 ii 段,不进位位置形如 A 的总方案数是
Ck1i1×Cnk1i×3n2×kC_{k-1}^{i-1}\times C_{n-k-1}^{i}\times 3^{n-2\times k}

类似的,

  • B:划分出 ii 段不进位的位置,方案数为 Cnk1i1C_{n-k-1}^{i-1},3 种填数方案的位置有 n2×k+1n-2\times k+1 个,最终对答案的贡献为
Ck1i1×Cnk1i1×3n2×k+1C_{k-1}^{i-1}\times C_{n-k-1}^{i-1}\times 3^{n-2\times k+1}
  • C:划分出 ii 段不进位的位置,方案数为 Cnk1i1C_{n-k-1}^{i-1},3 种填数方案的位置有 n2×k+1n-2\times k+1 个,最终对答案的贡献为
Ck1i1×Cnk1i1×3n2×kC_{k-1}^{i-1}\times C_{n-k-1}^{i-1}\times 3^{n-2\times k}
  • D:划分出 i1i-1 段不进位的位置,方案数为 Cnk1i2C_{n-k-1}^{i-2},3 种填数方案的位置有 n2×k+1n-2\times k+1 个,最终对答案的贡献为
Ck1i1×Cnk1i2×3n2×k+1C_{k-1}^{i-1}\times C_{n-k-1}^{i-2}\times 3^{n-2\times k+1}

枚举进位的位置划分出的段数 ii,对所有可能的情况分别求解再求和即可。

代码

#include <iostream>
#include <algorithm>
#include <math.h>
#include <stdio.h>
#include <map>
#include <set>
#include <queue>
using namespace std;
using LL=long long;
const int N=1000001;
const LL mod=1000000007;
LL poww(LL a,LL b)
{
	if (b<0) while (1);
	LL ans=1;
	for (;b;b>>=1,a=a*a%mod)
		if (b&1) ans=ans*a%mod;
	return ans;
}
int n,m,k;
char ch;
LL fac[N],inv[N];
LL C(int n,int m)
{
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
LL solve()
{
	scanf("%d%d",&n,&k);
	if (k==0) return poww(3,n);
	LL ans=0;
	for (int i=1;i<=k&&n-k-1>=i-2;++i)
	{
		if (i<=n-k-1&&n-2*i>=0) ans=(ans+C(k-1,i-1)*C(n-k-1,i)%mod*poww(3,n-2*i)%mod)%mod;
		if (i-1<=n-k-1&&n-2*i+1>=0) ans=(ans+C(k-1,i-1)*C(n-k-1,i-1)%mod*poww(3,n-2*i+1)%mod)%mod;
		if (i-1<=n-k-1&&n-2*i>=0) ans=(ans+C(k-1,i-1)*C(n-k-1,i-1)%mod*poww(3,n-2*i)%mod)%mod;
		if (i-2>=0&&n-2*i+1>=0) ans=(ans+C(k-1,i-1)*C(n-k-1,i-2)%mod*poww(3,n-2*i+1)%mod)%mod;
	}
	return ans;
}
int main()
{
	int T=1;
	n=1e6;
	fac[0]=1;
	for (int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mod;
	inv[n]=poww(fac[n],mod-2);
	for (int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
	while (T--) printf("%lld\n",solve());
	return 0;
}