JZOJ 5751 数位

传送门
题面

思路

唉,我太弱了,什么都会,DP 也不会,唉,我太弱啦!

一开始我在想,这个题目的样子长得这么像一个数位 DP,那这个题就一定是一个数位 DP 了。(所以说不要乱立 flag)结果 真·神犇 告诉我,这道题需要先将问题转换成可以填所有数,再 DP。总的来说这个 DP 跟数位 DP 无关,倒是跟 Prufer 序列中的某些题目中用的 DP 很像。

设答案为 a n s ,首先把问题转换成 a n s = f ( r ) f ( l 1 ) ,这个应该没有问题吧?大家都是明白人,这种套路应该就不用说了。

考虑一个数,在 r 以内。在数位 DP 中我们有顶格的概念,就是某些位置不能填所有数,以避免最后填出来的数超出 r 。我们枚举一个 x ,表示前 x 为是顶格的(即最高的前 x 位),换句话说,前 x 位与 r 的前 x 位一样。然后我们枚举第 x + 1 位填什么,显然它不能和 r 的第 x + 1 位一样,否则前 x + 1 位都是顶格的,与我们枚举的内容矛盾。我们考虑剩下的位。在不考虑特殊情况时,显然它们是可以在 0 B 1 中随便填的。

现在的问题就是,在剩下的 m r x 1 位中,有多少种填法使得最后得到的数是一个好数。考虑动态规划。由于我们不可能按位置依次填(否则状态太大无法表示),因此我们依次考虑每种数位。设 f i , j 表示考虑了 0 i 2 i 1 种数位,填了 j 个数进去的方案数。转移时,我们枚举这种数填多少个,不转移使得某种数位不合法的填的个数,最后得到的 f i + 1 , m r x 1 就是答案。

现在考虑特殊情况,即前面全是 0 的情况,因为前导零不计数,所以要特殊考虑。我们不妨最后再放 0 。放 0 的时候我们枚举一下前导 0 的个数,再进行转移。

最后的一位枚举就是了。

时间复杂度 O ( n 5 ) ,由于不可能枚举满,因此实际表现很好,跑得飞快。


时间复杂度分析:

首先,第二层循环因为并不是每一位都是 B 1 ,因此不能跑满,期望将时间除以 2 。第四层循环越来越短,内部还有一个小于四层循环的,相当于是平方数列求和,因此时间复杂度要除以 6 。因此时间复杂度为 O ( 1 2 m B × m 3 6 ) ,代入 50 后大约为两千万,还是可以的。

参考代码
#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 的提示。其中有一个 t 全为 0 的点,但是并没有什么用。如果它给出了一个保证每一位是 B 1 的点,或许这道题就切了。从中也能看出 Special Instance 的重要性。

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80511716
今日推荐