传送门
题面
思路
唉,我太弱了,什么都会,DP 也不会,唉,我太弱啦!
一开始我在想,这个题目的样子长得这么像一个数位 DP,那这个题就一定是一个数位 DP 了。(所以说不要乱立 flag)结果 真·神犇 告诉我,这道题需要先将问题转换成可以填所有数,再 DP。总的来说这个 DP 跟数位 DP 无关,倒是跟 Prufer 序列中的某些题目中用的 DP 很像。
设答案为 ,首先把问题转换成 ,这个应该没有问题吧?大家都是明白人,这种套路应该就不用说了。
考虑一个数,在 以内。在数位 DP 中我们有顶格的概念,就是某些位置不能填所有数,以避免最后填出来的数超出 。我们枚举一个 ,表示前 为是顶格的(即最高的前 位),换句话说,前 位与 的前 位一样。然后我们枚举第 位填什么,显然它不能和 的第 位一样,否则前 位都是顶格的,与我们枚举的内容矛盾。我们考虑剩下的位。在不考虑特殊情况时,显然它们是可以在 中随便填的。
现在的问题就是,在剩下的 位中,有多少种填法使得最后得到的数是一个好数。考虑动态规划。由于我们不可能按位置依次填(否则状态太大无法表示),因此我们依次考虑每种数位。设 表示考虑了 这 种数位,填了 个数进去的方案数。转移时,我们枚举这种数填多少个,不转移使得某种数位不合法的填的个数,最后得到的 就是答案。
现在考虑特殊情况,即前面全是 的情况,因为前导零不计数,所以要特殊考虑。我们不妨最后再放 。放 的时候我们枚举一下前导 的个数,再进行转移。
最后的一位枚举就是了。
时间复杂度 ,由于不可能枚举满,因此实际表现很好,跑得飞快。
时间复杂度分析:
首先,第二层循环因为并不是每一位都是 ,因此不能跑满,期望将时间除以 。第四层循环越来越短,内部还有一个小于四层循环的,相当于是平方数列求和,因此时间复杂度要除以 。因此时间复杂度为 ,代入 后大约为两千万,还是可以的。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0; bool positive = true;
char ch = getchar();
while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
if (ch == '-') { positive = false; ch = getchar(); }
while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20]; int length = 0;
if (x < 0) putchar('-'); else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
putchar('\n');
}
const int mod = int(1e9) + 7;
const int maxn = 55;
int B, ml, mr;
int t[maxn];
int a[maxn];
int b[maxn];
#define RunInstance(x) delete new x
struct brute
{
int app[maxn];
int ans;
void search(int* num, int depth, bool top, bool first)
{
depth--;
if (depth == -1)
{
bool bOk = true;
for (int i = 0; i < B; i++)
{
if (app[i] == t[i])
{
bOk = false;
break;
}
}
ans += bOk;
return;
}
int to = top ? num[depth] : B - 1;
for (int i = 0; i <= to; i++)
{
if (i || !first) app[i]++;
search(num, depth, top && i == num[depth], first && !i);
if (i || !first) app[i]--;
}
}
brute() : app(), ans()
{
search(b, mr, true, true);
int temp = ans;
ans = 0;
search(a, ml, true, true);
printOut(temp - ans);
}
};
struct work
{
LL counter;
int f[maxn][maxn];
void solve(int* num, int n)
{
int T[maxn];
memcpy(T, t, sizeof(T));
counter = 0;
// 前导 0
for (int o = 0; o < n - 1; o++)
{
int len = n - 1 - o;
f[0][0] = 1;
for (int i = 1; i < B; i++)
{
for (int j = 0; j <= len; j++)
{
f[i][j] = 0;
for (int k = 0; k <= j; k++) if (k != T[i] && f[i - 1][j - k])
{
f[i][j] = (f[i][j] + C[j][k] * f[i - 1][j - k]) % mod;
}
}
}
f[B][len] = 0;
for (int k = 0; k < len; k++) if (k != T[0] && f[B - 1][len - k])
{
f[B][len] = (f[B][len] + C[len - 1][k] * f[B - 1][len - k]) % mod;
}
counter = (counter + f[B][len]) % mod;
}
// 填最高位
for (int o = 1; o < num[n - 1]; o++)
{
T[o]--;
f[0][0] = 1;
for (int i = 0; i < B; i++)
{
for (int j = 0; j <= n - 1; j++)
{
f[i + 1][j] = 0;
for (int k = 0; k <= j; k++) if (k != T[i] && f[i][j - k])
{
f[i + 1][j] = (f[i + 1][j] + C[j][k] * f[i][j - k]) % mod;
}
}
}
counter = (counter + f[B][n - 1]) % mod;
T[o]++;
}
for (int x = n - 2; x; x--)
{
T[num[x + 1]]--;
for (int o = 0; o < num[x]; o++)
{
T[o]--;
f[0][0] = 1;
for (int i = 0; i < B; i++)
{
for (int j = 0; j <= x; j++)
{
f[i + 1][j] = 0;
for (int k = 0; k <= j; k++) if (k != T[i] && f[i][j - k])
{
f[i + 1][j] = (f[i + 1][j] + C[j][k] * f[i][j - k]) % mod;
}
}
}
counter = (counter + f[B][x]) % mod;
T[o]++;
}
}
// 填最低位
T[num[1]]--;
for (int i = 0; i <= num[0]; i++)
{
T[i]--;
bool bOk = true;
for (int j = 0; j < B; j++)
if (!T[j])
{
bOk = false;
break;
}
counter += bOk;
T[i]++;
}
}
LL C[maxn][maxn];
work() : C(), f()
{
for (int i = 0; i <= 50; i++)
{
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
solve(b, mr);
LL ans = counter;
solve(a, ml);
ans = (ans - counter + mod) % mod;
printOut(ans);
}
};
void run()
{
int T = readIn();
while (T--)
{
B = readIn();
ml = readIn();
mr = readIn();
for (int i = 0; i < B; i++)
t[i] = readIn();
for (int i = 0; i < ml; i++)
a[i] = readIn();
for (int i = 0; i < mr; i++)
b[i] = readIn();
a[0]--;
int cnt = 0;
while (a[cnt] < 0)
{
a[cnt] += B;
a[cnt + 1]--;
cnt++;
}
if (!a[ml - 1])
ml--;
RunInstance(work);
}
}
int main()
{
#ifndef LOCAL
freopen("digits.in", "r", stdin);
freopen("digits.out", "w", stdout);
#endif
run();
return 0;
}
总结
这道题做不来的一个重要原因可能是没有 Special Instance 的提示。其中有一个 全为 的点,但是并没有什么用。如果它给出了一个保证每一位是 的点,或许这道题就切了。从中也能看出 Special Instance 的重要性。