照看小猫

题目链接:nowcoder 217602

到主站看:https://blog.csdn.net/weixin_43346722/article/details/113064566

题目大意

要你给一些猫起名字,每一只猫的名字不能相同,而且每只猫有要求,要求它的名字长度不能超过它给定的一个值。问你有多少种方案。
名字只能由小写字母包含,方案数对 77797 取模。
如果没有方案,则输出 -1。

思路

这道题我们看到每个猫的最大长度很小,只有 ,我们考虑从这里入手。

那我们考虑枚举看看如果只有一只猫,它最长能起 的名字,问你有多少方案。
那很容易想到它的长度可以是 ,然后对于长度 ,每一位都可以放任意一个小写字母。小写字母有 个,那就是组合, 种方案。
那合在一起,就是这个:

我们可以预处理出来,而且你会发现上面的式子可以用前缀和来搞,那你就可以 预处理出这两个东西了。

我们考虑到如果一只猫起了一个名字,那对于另一只猫,它就可能会少一种能其的名字。
为什么说是可能呢?
因为如果第一只猫选了长度为 的名字,而第二只猫最多也只能选长度为 的,那第二只猫就不可能选到第一只猫的名字。

那你怎么保证让这一只现在的猫能选的名字都不会受到或都受到前面的猫的影响呢?(因为这样你就可以直接优化,用容斥或者直接算了)
可以看到都不受到影响似乎不太好弄,那我们考虑都受到影响。

那我们想想怎样会受到影响。
举个例子,如果前面的猫最多选长度为 的名字,然后这只猫最多选 的名字。那无论前面的猫选什么名字,它都包含在这只猫能选的名字里面,都会造成影响。
归纳起来,其实就是这只猫最长能选的名字长度要大于等于前面的所有猫最长能选的名字长度。

那其实就是从小到大排个序的事情。

那我们有容斥,它能选的方案数就是它一只能选的方案数减去它前面的数的个数。
当然,没有解的时候就是它能选的方案数小于等于 ,就说明它能选的所有名字都被前面的猫领完了。

那这样我们把每只猫能选的方案数乘起来,就是要的答案。

不过要记得再过程中取模。取模之后有一个事情就是在取模意义下,你减出来方案数小于 并不是真的小于 ,根据观察数据可以看出,要当最大长度小于等于三的时候,才不会有取模产生数的改变。而到第四个的时候,已经是八万多,方案数小于等于 已经是不可能的了。

代码

#include<cstdio>
#include<algorithm>
#define mo 77797

using namespace std;

int n, a[10001];
long long ans, di[11], now, one[11];

bool cmp(int x, int y) {
    return x < y;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }

    di[0] = 1ll;//预处理出 26^i
    for (int i = 1; i <= 10; i++) {
        di[i] = di[i - 1] * 26ll;
        if (di[i] > mo) di[i] %= mo;
    }

    //预处理出单独给一只猫起一个长度不超过 i 的名字的方案数(不考虑名字重叠)
    for (int i = 1; i <= 10; i++) {
        one[i] = one[i - 1] + di[i];
        if (one[i] > mo) one[i] %= mo;
    }
    one[0] = 0;

    sort(a + 1, a + n + 1, cmp);//按长度大小排序

    ans = 1;
    for (int i = 1; i <= n; i++) {
        if (one[a[i]] - i + 1 <= 0 && a[i] <= 3) {//没有解,一定会有重叠的名字
            printf("-1");
            return 0;
        }
        ans = ans * (((one[a[i]] - i + 1) % mo + mo) % mo);//容斥求这个猫可以选的名字的数量
        if (ans > mo) ans %= mo;
    }

    if (ans == 0) printf("-1");
        else printf("%lld", ans);

    return 0;
}