1.题目描述:点击打开链接
2.解题思路:本题利用数位dp解决。即通过寻找数位上的递推式来加以解决。从样例可知,如果只是一个个的枚举,时间上肯定是承受不了的,必须通过寻找递推式来加以解决。通过观察,我们发现,如果一个整数n能够被k整除,即n%k==0,假如这个数的二进制形如1XXX这样的二进制,也就意味着二进制数1000的值模k的余数和二进制数XXX的值模k的余数之和正好为0。这样的话我们设d(cur,m,MOD)来表示cur位二进制数中有m个1,且它们的值模k均为MOD的数的个数。那么本题的最终答案就是d(n-1,(n-1)/2,(k-mid[n])),因为第n位有一个1,前n-1位只需要(n-1)/2个1即可。
那么问题转化为如何快速求解d(cur,m,MOD)。分两种情况:(1)如果第cur为填写0,那么结果是d(cur-1,m,MOD)。(2)如果第cur位填写1,那么结果是d(cur-1,m-1,(MOD-mod[cur]))(mod[cur]表示长度为cur,第一位为1,后面均为0的二进制数模k的结果。程序中为了防止余数变为负数,多了一个加k后模k的部分)。根据加法原理即得到下列递推式:
d(cur,m,MOD)=d(cur-1,m,MOD)+d(cur-1,m-1,MOD-mod[cur])(cur>0)
那么当cur==0时到达了递归边界,此时的MOD应该满足MOD%k==0,返回1,否则返回0。
本题的数位dp的设置方式值得我们学习。
3.代码:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<algorithm> #include<string> #include<sstream> #include<set> #include<vector> #include<stack> #include<map> #include<queue> #include<deque> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<functional> using namespace std; #define SF scanf #define PF printf typedef long long LL; const int MAXN = 70; const int MAXK = 100; LL d[MAXN + 10][MAXN * 2 + 10][MAXK + 10], mod[MAXN + 10]; LL n, K; LL dp(int cur, int O, int MOD)//前cur位,有O个1,这些数的余数均为MOD的数的个数 { if (cur == 0) { if (O) return 0; return d[cur][O][MOD] = ((MOD % K == 0) ? 1 : 0);//cur为0的时候,MOD模k的余数应当为0,如果是,返回1,否则返回0 } if (d[cur][O][MOD] != -1) return d[cur][O][MOD]; LL &ans = d[cur][O][MOD]; ans = dp(cur - 1, O, MOD);//第cur位填写零的个数 if (O > 0) ans += dp(cur - 1, O - 1, (MOD - mod[cur] + K) % K);//第cur位填写1的个数,那么前cur-1位有O-1个1 return ans; } void init()//事先计算二进制数1000...模k的余数 { mod[1] = 1; for (int i = 2; i <= MAXN; i++) mod[i] = (mod[i - 1] << 1) % K; } int main() { //freopen("t.txt", "r", stdin); int T, kase = 1; for (SF("%d", &T); kase <= T; kase++) { memset(d, -1, sizeof(d)); cin >> n >> K; PF("Case %d: ", kase); if (K == 0 || (n & 1)) { cout << 0 << '\n'; continue; } init(); LL ans = dp(n - 1, (n - 1) / 2, (K - mod[n]) % K);//最终的结果是前n-1位(最低位为1),且余数为k-mod[n]的二进制数的个数,因为这样的数与n位二进制数(1000...)的余数mod[n]相加恰好为0 cout << ans << '\n'; } }