[一本通1683]稗田阿求

题目描述

在幻想乡,稗田乙女是负责书写《幻想乡缘起》的家族。由于需要代代相传关于幻想乡的记忆,稗田乙女采用了一些特殊的记录方式。对于相同重复的文字,稗田乙女会用一个数字来代替,然后用一个数列来表示一个段文字。比如 \(1\)代表“\(A\)”,\(2\)代表“\(C\)”,那么\(\{1,2\}\)就代表“\(AC\)”,\(\{2,1,2\}\)就代表“\(CAC\)”。不过由于年代过于久远,到阿求时已经是第九代稗田乙女,所以难免会出现错误。现在阿求有\(N\)个数字\((1..N)\)\(N\)个字符(“\(A\)”..第\(N\)个字母),以及一些以前传承下来的M组文字段和对应的数列。每一组文字段和数列相互对应,文字的第\(i\)个字符对应着数列的第\(i\)项。阿求想要知道怎样安排\(N\) 个数字和字符的对应关系,能够使组数尽可能多的文字段和数列组合满足该对应关系。数字和字符间一一对应,不会出现多对一或一对多的情况。

输入

第1行:\(2\)个正整数\(N, M\)
\(2..2M+1\)行:每\(2\)行为一组,第\(1\)行为文字段落,第\(2\)行为数列。保证文字段落的字符数\(L\)等于数列数字个数\(L\),且均在\(1..N\)。文字段落只包含大写字母。

输出

第1行:最多能够匹配的文字段落和数列组合数量。

输入样例

3 3 
ACCA 
1 3 3 1   
AAC 
2 2 1 
BCBC 
3 1 3 1

输出样例

2

提示

样例解释

\(A=2,B=3,C=1\)时第\(2、3\)字符串和数列组合满足对应关系。

数据规模

对于60%的数据:\(1≤N≤10,1≤M≤20\)
对于100%的数据:\(1≤N≤26,1≤M≤40,1≤L≤100\)

注意事项

保证每一组文字段和数列组合均合法,在一组文字段和数列组合里面不会出现多个字符对一个数字,或是一个字符对多个数字的情况。

思路

本题的数据比较小,首先初始化好每个文本的不同字符对应的数字的情况。
然后枚举两两配对的情况,并且用状态压缩存起来。
其中有个可以优化的地方,当检测到之后的完全都可匹配上也没有最优解好时,可以直接退出。

代码

话不多说,直接上代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

char str[128];

int n, m;

int view[45][28]; //字符串中字母对应的数字

bool vis[28]; //标记被占用的字母

unsigned long long mate[45]; //状态压缩表示相容的字符串编号

int ans = 0;

inline bool AStar(int u, int cnt) //判断最好情况下能否超越当前最优解
{
    return cnt + m - u + 1 >= ans;
}

void dfs(int u = 1, int cnt = 0, unsigned long long st = -1LL)
{
    if (!AStar(u, cnt))
        return; //无论如果都无法超越当前最优解
    if (u > m)
    {
        ans = max(ans, cnt);
        return;
    }
    if (st & 1LLU << (u - 1))              //相容
        dfs(u + 1, cnt + 1, st & mate[u]); //收录
    dfs(u + 1, cnt, st);                   //不收录
}

int main()
{
    scanf(" %d %d", &n, &m);
    for (int i = 1; i <= m; i++)
    {
        scanf(" %s", str + 1);
        for (int j = 1; str[j] != 0; j++)
        {
            scanf(" %d", &view[i][str[j] - 'A']);
        }
    }
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= m; j++)
            if (i != j)
            {
                for (int k = 0; k < 26; k++)
                    if (view[i][k] && view[j][k] && view[i][k] != view[j][k])
                    {
                        goto End;
                    }
                memset(vis, 0, sizeof(vis));
                for (int k = 0; k < 26; k++)
                    if (view[i][k] || view[j][k])
                    {
                        int t;
                        if (!view[i][k])
                            t = view[j][k];
                        else
                            t = view[i][k];
                        if (!vis[t])
                            vis[t] = true;
                        else //之前有字母占用了
                        {
                            goto End;
                        }
                    }
                mate[i] |= 1LLU << (j - 1);
            End:;
            }
    dfs();
    printf("%d", ans);
    return 0;
}
posted @ 2021-08-12 20:30  Icys  阅读(284)  评论(0编辑  收藏  举报