能被一个整数整除的二进制序列个数(SOJ 2009)
问题:给出一个$N$位($N\le64$且为偶数)的二进制数并且没有前导$0$,且$0$和$1$的个数都是$N/2$.给出另一个整数$K$,求解满足上述条件并且能被$K$整除的二进制数的个数。
分析:动态规划。定义$dp[i][j][k]$为前$i$位中有$j$个$1$并且对$K$取模等于$k$的二进制序列个数,则根据第$i$位是否为$1$构建状态转移方程:
(1)第$i$位为$0$,则$dp[i][j][k]=dp[i-1][j][k]$;
(2)第$i$位为$1$,则$dp[i][j][k]=dp[i-1][j-1][x]$,这里关键是$x$的取值!前$i$位对$K$取模有两个部分:前$i-1$位和第$i$位,定义$c$为第i位对$K$的取模结果,$c=2^{N-i}\%K$,则
$(x+c)\%K=k, 0\le x<K, 0\le c<K$,
$x+c$只可能等于$k$或$K+k$.若$c\le k$, 则$x=k-c$;否则$x=K+k-c$.
总的状态转移方程为:
$dp[i][j][k] = dp[i - 1][j][k]+(c<=k ? dp[i-1][j-1][k-c] : dp[i-1][j-1][K+k-c])$.
注意:初始化所有$dp[i][j][k]=0$,只有$dp[1][1][2^{N-1}\%K]=1$.C++中$long\ long$的最大值为$2^{63}-1$,这里是$2^{63}$不能用$long\ long$变量表示,好在是取模,可以很容易解决这个问题。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
long long dp[66][34][101];
int main()
{
int T, N, K;
scanf("%d", &T);
int t;
int i, j, k;
int jS;
long long a=1;
int c;
for (t = 1; t <= T; t++)
{
scanf("%d%d", &N, &K);
if (N % 2==1 || K==0)
{
printf("Case %d: 0\n",t);
continue;
}
memset(dp, 0, sizeof(dp));
c = ( (a << (N - 2)) % K * (2%K) )%K;
dp[1][1][c] = 1;
for (i = 2; i <= N; i++)
{
jS = min(i, N / 2);
c = (a << (N - i))%K;
for (j = 1; j <= jS; j++)
for (k = 0; k < K; k++)
dp[i][j][k] = dp[i - 1][j][k]+(c<=k ? dp[i-1][j-1][k-c] : dp[i-1][j-1][K+k-c]);
}
printf("Case %d: %lld\n", t,dp[N][N/2][0]);
}
return 0;
}