1 条题解

  • 0
    @ 2026-10-26 7:13:55

    思路与算法

    每组文字和数列定义一个部分的一一映射:字母到数字,以及数字到字母。题目保证单组内部合法。两组相容,当且仅当共同字母没有指向不同数字、共同数字没有来自不同字母。以每组为一个顶点,两组相容就连边,要求最多可同时满足多少组,转成这个图的最大团大小。

    最多40个顶点,折半为左右各至多20个。右半为每个子集S预处理dp[S]:其内部最大团大小。取S里任意顶点v,令T=S去掉v,则dp[S]=max(dp[T],1+dp[T与v的右半邻居交集]),分别表示不选v或选v。

    左半枚举所有子集,递推判断它本身是否为团,同时维护其所有顶点在右半的公共邻居allowed[S]。若左子集是团,就可与右半allowed[S]中的任意团组合,候选答案为左侧人数加dp[allowed[S]]。空左子集也要覆盖,初始答案取完整右半的dp。

    正确性说明

    如果若干组能被同一个完整对应关系同时满足,那么任意两组对同一字母、同一数字的约束都不冲突,所以它们两两相容。反过来,如果若干组两两相容,所有部分映射的并集是一个一致且单射的部分对应关系:任何冲突都会在产生这两个约束的某两组间出现。尚未分配的字母和数字数量相等,可任意配对补全。因此两两相容充要,最大满足组数等于相容图最大团。

    对于右半子集S,一个最优团要么不包含v,大小至多dp[T];要么包含v,其余顶点必须在T中且都邻接v,最优大小正是1+dp[T与邻居交集]。两类方案均可构造,所以递推准确。引用的子集都比S小,按掩码递增计算有效。

    左半子集S从T再加v,它为团当且仅当T是团且T每个顶点都邻接v;右侧公共邻居也恰为allowed[T]与v右侧邻居的交集。因而两项递推均正确。每个完整团都能唯一分为左、右两部分,右部必位于左部公共邻居中;枚举对应左子集时,dp提供不差于其右部的最优选择。所有候选组合又确实为团,故枚举得到全局最优值。

    复杂度

    设左右顶点数为L、R,L+R=M且均不超过20。读取并建立组内映射O(M字符串长度+MN),两两相容检查O(M²N)。两侧子集分别作常数次位运算与转移,O(2^L+2^R)。总时间O(M最大长度+M²N+2^L+2^R),额外空间O(MN+2^L+2^R),包含映射和子集数组初始化。右最优值及左合法标记用unsigned char,公共邻居掩码用unsigned,两个20位子集的主体数组约6MiB,符合原64MiB限制。

    易错点

    1. 目标按整组是否满足计数,不能把每个字符匹配次数当权值。
    2. 除字母到数字外,还必须检查数字到字母,防止多字母共用一个数字。
    3. 不同组可相同,输入仍表示不同组;相同约束的多组可以一起计入答案,不能去重删除顶点。
    4. 右dp存子集内最优团,子集本身不必是团;左侧只有本身为团的子集才能参与组合。
    5. 空左子集、单组、N=1、全部40组相容、完整映射互相冲突和随机部分约束均覆盖。
    6. 使用最多20位的半边掩码,避免用32位变量直接表示40个组。

    参考代码(C++11)

    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n, m;
        cin >> n >> m;
        vector<vector<int>> f(m, vector<int>(n, -1)), inv = f;
        for (int i = 0; i < m; ++i) {
            string s;
            cin >> s;
            for (char c : s) {
                int d;
                cin >> d;
                --d;
                f[i][c - 'A'] = d;
                inv[i][d] = c - 'A';
            }
        }
        int L = m / 2, R = m - L;
        vector<unsigned> al(L), ar(R), cross(L);
        for (int i = 0; i < m; ++i)
            for (int j = i + 1; j < m; ++j) {
                bool ok = true;
                for (int x = 0; x < n; ++x) {
                    if (f[i][x] >= 0 && f[j][x] >= 0 && f[i][x] != f[j][x])
                        ok = false;
                    if (inv[i][x] >= 0 && inv[j][x] >= 0 && inv[i][x] != inv[j][x])
                        ok = false;
                }
                if (ok) {
                    if (j < L) {
                        al[i] |= 1u << j;
                        al[j] |= 1u << i;
                    } else if (i >= L) {
                        ar[i - L] |= 1u << (j - L);
                        ar[j - L] |= 1u << (i - L);
                    } else
                        cross[i] |= 1u << (j - L);
                }
            }
        vector<unsigned char> dp(1u << R);
        for (unsigned s = 1; s < dp.size(); ++s) {
            int v = __builtin_ctz(s);
            unsigned t = s & (s - 1);
            dp[s] = max((int)dp[t], 1 + (int)dp[t & ar[v]]);
        }
        vector<unsigned char> valid(1u << L);
        vector<unsigned> allowed(1u << L);
        valid[0] = 1;
        allowed[0] = (1u << R) - 1;
        int ans = dp.back();
        for (unsigned s = 1; s < valid.size(); ++s) {
            int v = __builtin_ctz(s);
            unsigned t = s & (s - 1);
            allowed[s] = allowed[t] & cross[v];
            valid[s] = valid[t] && !(t & ~al[v]);
            if (valid[s])
                ans = max(ans, __builtin_popcount(s) + (int)dp[allowed[s]]);
        }
        cout << ans << '\n';
    }
    
    • 1

    信息

    ID
    1686
    时间
    1000ms
    内存
    64MiB
    难度
    提高+
    标签
    递交数
    1
    已通过
    1
    上传者