CF 997B Roman Digits

传送门
题目大意

现在规定罗马数字只有 4 个字符:I、V、X、L,分别代表 1 5 10 50 。规定:一个罗马数字的值为该数字包含的字符代表的数字之和,而与字符的顺序无关(这与实际情况是不一样的!)。

问一个长度为 n 的罗马数字可以有多少种不同的值。显然答案不会超过 50 n ,所以不对任何数取模。

n 10 9

思路

考虑生成函数。最 Naive 的想法是:求 ( x + x 5 + x 10 + x 50 ) n 有多少系数非 0 项,显然不可做。

注意到, 5 × 1 + 1 × 50 = 1 × 5 + 5 × 10 ,就是说五个 1 加上一个 50 等于一个 5 加上五个 10 。如果不存在这个结论,即如果一个长度为 n 的罗马数字的值只对应一个四元组 ( x 1 , x 5 , x 10 , x 50 ) ,那么答案显然为 C n + 4 1 4 1 (我靠洛谷垃圾翻译把 L 写成 100 )。

考虑 5 × 1 + 1 × 50 或者 1 × 5 + 5 × 10 。我们规定这种相等的只能取前者,也就是说 x 5 1 x 10 5 不能同时成立。那么考虑分 x 5 = 0 x 5 1 两种情况讨论。当 x 5 = 0 时,剩下三种随便选,对答案的贡献为 C n + 2 2 。当 x 5 1 时,我们首先枚举 x 5 选了多少个,然后枚举 x 10 选了多少个,这一部分的方案数为:

x 5 = 1 x 10 = 0 4 C n x 5 x 10 + 1 1

x 5 = 1 x 10 = 0 4 ( n x 5 x 10 + 1 ) [ n x 5 x 10 0 ]

然后妥妥地错掉了……那肯定是还有东西算重了,看看还有没有相等的情况。

果然有: 9 × 5 = 5 × 1 + 4 × 10 ……那么我们同样规定只能出现一侧的值。由于现在有些混乱,因此我们重新做一次……


现在有:

5 × 1 + 1 × 50 = 1 × 5 + 5 × 10

9 × 5 = 5 × 1 + 4 × 10

这样好了,我们规定第一个等式的右边必须化为左边,第二个等式的左边必须化为右边,则下面两个式子必须不成立:

x 5 1  且  x 10 5

x 5 9

那么现在 x 5 必须在 0 8 中选一个,我们用同样的思路去做:当 x 5 = 0 时,贡献还是 C n + 2 2 ;当 x 5 1 时,我们就不用考虑等差数列了,直接枚举 x 5 x 10 即可。

然后妥妥地错掉了……那肯定还有东西算重了,看看还有没有相等的情况。

果然有: 8 × 5 + 1 × 50 = 9 × 10 ……我也是醉了,那就让右边化为左边,这样的话就要满足三个式子始终不成立:

x 5 1  且  x 10 5

x 5 9

x 10 9

还是枚举这两个,这下跑就对了。

参考代码

我去哦三行代码写了一个上午。

#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 LL 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);
}

int n;

void run()
{
    n = readIn();
    LL ans = 0;
    for (int i = 0; i <= 8 && i <= n; i++)
        for (int j = 0; j <= (i ? 4 : 8) && i + j <= n; j++)
            ans += n + 1 - i - j;
    printOut(ans);
}

int main()
{
    run();
    return 0;
}
总结

比赛时肯定打表啊!当 n 12 时,答案呈线性增长(观察代码,显然),打表真是轻松……

关键是发现问题后没有及时找到问题(显然是还有算重的情况,但是没有及时找到一组新的)。可能这种问题不是很好避免,所以比赛时最好先写暴力,然后用暴力拍,发现问题后再及时改。不过这样可能时间又不够了……

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80957934