[SMOJ1711]多滋味的咖啡

题目描述

N 头奶牛围绕着一个圆桌坐着,农夫FJ有 N 杯咖啡,第 i 杯咖啡的口味是 tastei,其中 tastei 是 A 或 B 或 C 三个字母之一。FJ给每头奶牛分配一杯咖啡(注意:第 i 头奶牛不一定是得到第 i 杯咖啡,这是由农夫FJ决定的事情),FJ有一个条件:相邻的两头奶牛不能得到相同口味的咖啡,那么FJ总共有多少种不同的分配方案呢?答案模1,000,000,007。

输入格式 1711.in

 多组测试数据。
第一行,一个整数 G ,表示有 G 组测试数据。 1G5
每组测试数据格式如下:
第一行,一个整数 N N50
第二行, N 个大写字母,第 i 个字母表示第 i 杯咖啡的口味。

输出格式 1711.out

G 行,每行一个整数。

输入样例 1711.in

4
4
ABAB
5
ABABA
3
ABC
35
BCBABBACBABABCCCCCAABBAACBBBBCBCAAA

输出样例 1711.out

2
0
6
741380640

样例解释

第一组测试数据:
方案一:
第1头奶牛A,第2头奶牛B,第3头奶牛A,第4头奶牛B。
方案二:
第1头奶牛B,第2头奶牛A,第3头奶牛B,第4头奶牛A。


首先明确一点,我们的任务是给奶牛安排咖啡,因此我们关心的是如何分配,即各口味的咖啡数量是我们重点考虑的内容,因此输入时只需统计各口味的咖啡数量即可,位置不造成影响。

看到方案计数类问题,很自然地就会联想到dp。如何表示状态呢?如果只记录当前奶牛编号 i 和当前咖啡口味 j 的话,显然是没有办法进行转移的,因为我们只知道下一杯应当放哪些口味,但却不确定能不能放这种口味,原因是各口味咖啡剩余数量不得而知。顺理成章地就会想到要再加上三种咖啡各自的剩余数量。

于是我们用 f[i][j][A][B][C] 表示到前 i 头奶牛,其中第 i 头奶牛取口味 j 的咖啡,A 口味咖啡剩余 A 杯,B口味剩余 B 杯,C口味剩余 C 杯时的方案数。考虑到是一张圆桌,相邻的又不能相同,那么第一杯是会影响最后一杯的。因此所求即为 f[N][i][A][B][C]+f[N][j][A][B][C] ,其中 i j 是需要根据第一杯咖啡的口味进行分类讨论的, A 表示A种口味咖啡的总数量,依此类推。

状态的转移也非常容易,进行简单的分类讨论即可,共有6种情况:
- 第 i 杯取 A 口味,第 i+1 杯取 B 口味
- 第 i 杯取 A 口味,第 i+1 杯取 C 口味
- 第 i 杯取 B 口味,第 i+1 杯取 A 口味
- 第 i 杯取 B 口味,第 i+1 杯取 C 口味
- 第 i 杯取 C 口味,第 i+1 杯取 A 口味
- 第 i 杯取 C 口味,第 i+1 杯取 B 口味
再注意一下对杯数的限制,就可以了。总的时间复杂度为 O(n4) (分类讨论不计)。

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const long long mod = 1e9 + 7;
const int maxn = 52;

int n;
int cnt[3];
int dp[maxn][3][maxn][maxn][maxn];

int main(void) {
    freopen("1711.in", "r", stdin);
    freopen("1711.out", "w", stdout);
    int g;
    cin >> g;
    while (g--) {
        int n;
        cin >> n;
        memset(cnt, 0, sizeof cnt);
        for (int i = 0; i < n; i++) {
            char ch;
            cin >> ch;
            ++cnt[ch - 'A'];
        }
        long long ans = 0;
        for (int start = 0; start < 3; start++) { //枚举第一杯咖啡的口味
            if (!cnt[start]) continue; //没有此种咖啡,不考虑此种情况
            memset(dp, 0, sizeof dp);
            if (start == 0) dp[0][0][1][0][0] = 1;
            else if (start == 1) dp[0][1][0][1][0] = 1;
            else dp[0][2][0][0][1] = 1;
            //以下是各种分类讨论
            for (int i = 0; i + 1 < n; i++) {
                for (int a = 0; a < cnt[0]; a++)
                    for (int b = 1; b <= cnt[1]; b++)
                        for (int c = 0; c <= cnt[2]; c++) {
                            dp[i + 1][0][a + 1][b][c] += dp[i][1][a][b][c];
                            dp[i + 1][0][a + 1][b][c] %= mod;
                        }
                for (int a = 0; a < cnt[0]; a++)
                    for (int b = 0; b <= cnt[1]; b++)
                        for (int c = 1; c <= cnt[2]; c++) {
                            dp[i + 1][0][a + 1][b][c] += dp[i][2][a][b][c];
                            dp[i + 1][0][a + 1][b][c] %= mod;
                        }
                for (int a = 1; a <= cnt[0]; a++)
                    for (int b = 0; b < cnt[1]; b++)
                        for (int c = 0; c <= cnt[2]; c++) {
                            dp[i + 1][1][a][b + 1][c] += dp[i][0][a][b][c];
                            dp[i + 1][1][a][b + 1][c] %= mod;
                        }
                for (int a = 0; a <= cnt[0]; a++)
                    for (int b = 0; b < cnt[1]; b++)
                        for (int c = 1; c <= cnt[2]; c++) {
                            dp[i + 1][1][a][b + 1][c] += dp[i][2][a][b][c];
                            dp[i + 1][1][a][b + 1][c] %= mod;
                        }
                for (int a = 1; a <= cnt[0]; a++)
                    for (int b = 0; b <= cnt[1]; b++)
                        for (int c = 0; c < cnt[2]; c++) {
                            dp[i + 1][2][a][b][c + 1] += dp[i][0][a][b][c] %= mod;
                            dp[i + 1][2][a][b][c + 1] %= mod;
                        }
                for (int a = 0; a <= cnt[0]; a++)
                    for (int b = 1; b <= cnt[1]; b++)
                        for (int c = 0; c < cnt[2]; c++) {
                            dp[i + 1][2][a][b][c + 1] += dp[i][1][a][b][c];
                            dp[i + 1][2][a][b][c + 1] %= mod;
                        }
            }
            if (start == 0) (ans += (dp[n - 1][1][cnt[0]][cnt[1]][cnt[2]] + dp[n - 1][2][cnt[0]][cnt[1]][cnt[2]]) % mod) %= mod;
            else if (start == 1) (ans += (dp[n - 1][0][cnt[0]][cnt[1]][cnt[2]] + dp[n - 1][2][cnt[0]][cnt[1]][cnt[2]]) % mod) %= mod;
            else (ans += (dp[n - 1][0][cnt[0]][cnt[1]][cnt[2]] + dp[n - 1][1][cnt[0]][cnt[1]][cnt[2]]) % mod) %= mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值