第十二届北航程序设计竞赛决赛网络同步赛 B题 前前前世(数论推导 + DP)
题目链接 2016 BUAA-Final Problem B
考虑一对可行的点$(x, y)$
根据题意,设$x = ak + 1,y = bk + 1$
又因为$x$是$y$的祖先的祖先的祖先,所以$y = 8x + d, 0 <= d <= 7$;
那么代入到之前的那个式子
$y = 8x + d$
$= 8(ak + 1) + d = 8ak + d + 8$
注意到$8ak$对$k$取模后值为$0$,那么如果要满足题意,$d + 8$对$k$取模后值必须为$1$。
又因为$0 <= d <= 7$,所以$8 <= d + 8 <= 15$。
由此发现,当$k >= 15$时,无论$d$在取值范围内取什么值,都满足不了这个条件。
那么$k >= 15$时我们直接判无解。
根据同余的性质我们发现只需要关心根结点对$k$取模之后的值就行,
那么设$f[i][j][k]$为考虑根结点编号对$k$取模为$j$,模数为$k$,树的高度为$i$的时候这棵树的符合题意的点对数。
转移的时候从两个儿子那里获取信息,再加上自己的后代的后代的后代中符合题意的点的个数(前提是自己的编号对$k$取模也得为$1$)
那么状态数有$k^{2}n$个,用记忆化搜索实现就好了。
时间复杂度$O(k^{2}n)$
#include <cstdio> #include <cstring> #include <iostream> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; const int N = 5e4 + 10; const LL mod = 1e9 + 7; LL k, p; LL f[N][16][16]; LL c[20][20]; int T; int n; LL dp(int i, int j, int k){ if (~f[i][j][k]) return f[i][j][k]; if (i <= 3) return f[i][j][k] = 0; LL ret = 0; ret += dp(i - 1, 2 * j % k, k); ret %= mod; ret += dp(i - 1, (2 * j + 1) % k, k); ret %= mod; if (j % k == 1){ ret += c[8 * j % k][k]; ret %= mod; } return f[i][j][k] = ret; } int main(){ memset(f, -1, sizeof f); rep(i, 0, 15){ rep(k, 1, 15){ rep(j, i, i + 7){ if (j % k == 1){ ++c[i][k]; } } } } scanf("%d", &T); while (T--){ scanf("%lld%d%lld", &k, &n, &p); if (k >= 15){ puts("0"); continue; } p %= k; printf("%lld\n", dp((int)n, (int)p, (int)k)); } return 0; }