「解题报告」UOJ552 [UNR #4] 同构判定鸭

发布时间 2023-04-13 08:22:47作者: APJifengc
print("Same")

嗯。期望得分 100。

首先考虑到题目要求所有字符串的出现次数相同,这意味着两个图能表示出来的字符串的多重集相等。

先考虑有向无环图的情况,发现这时候这个多重集一定是一个有限集,且字符串的长度不超过 \(\min(n_1, n_2)\)。判定两个集合是否相等,考虑哈希。我们可以将字符串的哈希定义为每一位的哈希值相乘再相加,这样容易每次给集合统一拓展一位。每一位的哈希值可以根据位置与字符随机一个权值。设 \(f_{u, k}\) 表示从 \(u\) 节点开始的所有长度为 \(k\) 的字符串的哈希之和,那么这个东西是容易在 \(O(nm)\) 的复杂度内递推出来的。

那么我们此时只需要找到第一个集合不相等的长度,然后贪心填每一位判定。具体判定方法就是,设 \(g_u\) 为以 \(u\) 结尾的能匹配上当前的字符串 \(s\) 的路径数,这样我们只需要判定 \(\sum g_u f_{u, k}\) 是否相等即可。\(g_u\) 也是容易递推的。

那么对于一般图呢?可以猜测,一般图下最大长度也不会很长,不然也没法让你输出方案。实际上,最长的长度为 \(n_1 + n_2\),那么就直接套用上面的做法即可。

两个问题:

为什么最长的长度为 \(n_1 + n_2\)

我们回归最开始的描述,即每个字符串的出现次数相等。对于一个字符串来说,我们是容易通过递推求出出现次数的,而同时我们发现,这个递推过程是线性的。也就是说,我们可以使用矩阵来刻画这个转移过程。

我们将两个图合并在一起考虑,这样我们要求的就是 \(\sum_{i=1}^{n_1} f(i) = \sum_{i=n_1 + 1}^{n_1 + n_2} f(i)\)。我们将 \(f\) 写成横向量 \(u\),那么对于每一个字符,我们都可以用矩阵的方式去描述它,也就是说对于一个字符串 \(s_1s_2\cdots s_k\),我们可以将结果表示为 \(a = u M_{s_1} M_{s_2} \cdots M_{s_k}\),那么令 \(b\) 为一个前 \(n_1\) 位为 \(1\),后 \(n_2\) 位为 \(-1\) 的向量,那么两个出现次数不相等当且仅当 \(a^Tb \ne 0\)。这其实意味着 \(a, b\) 不正交。假如我们设 \(S_k\) 为所有长度不超过 \(k\) 的字符串最后的向量集合,那么我们要求的就是 \(\exists a \in S_k, a^T b \ne 0\)。这等价于 \(\exists a \in \mathrm{span}(S_k), a^T b \ne 0\)

而我们发现,随着 \(k\) 的增加,一定有 \(\mathrm{span}(S_{k - 1}) \subseteq \mathrm{span}(S_{k})\),这意味着 \(\mathrm{span}(S_k)\) 要不然维度增加,要不然永远不变。而维度最大值显然只有 \(n_1 + n_2\),这也就证明了长度上界为 \(n_1 + n_2\)

上述哈希正确率是否很高?

确实。

简单分析:如果我们将随机的权值看作变量 \(x_{k, c}\),那么我们的哈希函数就是关于 \(x_{k, c}\) 的一个 \(n_1 + n_2\) 次多项式。

Schwartz-Zippel 引理:对于某个域 \(F\) 上的不超过 \(d\) 次的多项式 \(f(x_1, \dots, x_m)\),如果每个 \(x_1, \cdots, x_m\) 都是从 \(F\) 中的一个大小为 \(S\) 的子集中独立地均匀随机选取的,那么 \(f(x) = 0\) 的概率不超过 \(\frac{d}{S}\)

那么我们可以得知,假如模数为 \(p\),如果我们随机 \(x_{k, c}\),那么单次碰撞的概率为 \(O(\frac{n_1 + n_2}{p})\)。而我们需要计算 \(O((n_1 + n_2) \Sigma)\) 次,根据 Union Bound,可以得知总碰撞概率为 \(O(\frac{(n_1 + n_2)^2 \Sigma}{p})\)。所以我们只需要取一个较大的 \(p\)(或者选取双哈希),碰撞概率就很低了。

推荐个 unsigned long long 级别的好记的模数:0xffff_ffff_0000_0001

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef __uint128_t hash_t;
const hash_t P = 0xffffffff00000001;
hash_t val[MAXN][26];
int len;
struct Graph {
    int n, m;
    vector<pair<int, int>> e[MAXN];
    void init() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int u, v; char c;
            scanf("%d%d %c", &u, &v, &c);
            e[u].push_back({v, c - 'a'});
        }
    }
    hash_t f[MAXN][MAXN];
    hash_t a[MAXN], b[MAXN];
    void calc() {
        for (int u = 1; u <= n; u++) f[u][0] = 1;
        for (int k = 1; k <= len; k++) {
            for (int u = 1; u <= n; u++) {
                for (auto [v, c] : e[u]) {
                    f[u][k] = (f[u][k] + val[k][c] * f[v][k - 1]) % P;
                }
            }
        }
        for (int i = 1; i <= n; i++)
            b[i] = 1;
    }
    hash_t get(int x) {
        hash_t ret = 0;
        for (int i = 1; i <= n; i++) {
            ret = (ret + b[i] * f[i][x]) % P;
        }
        return ret;
    }
    void confirm() {
        for (int i = 1; i <= n; i++) {
            a[i] = b[i];
        }
    }
    void test(int ec) {
        for (int u = 1; u <= n; u++) {
            b[u] = 0;
        }
        for (int u = 1; u <= n; u++) {
            for (auto [v, c] : e[u]) if (c == ec) {
                b[v] = (b[v] + a[u]) % P;
            }
        }
    }
} g1, g2;
char ans[MAXN];
int main() {
    // freopen("ex_string4.in", "r", stdin);
    g1.init();
    g2.init();
    len = g1.n + g2.n;
    mt19937_64 Rand(chrono::system_clock::now().time_since_epoch().count());
    for (int i = 1; i <= len; i++) {
        for (int j = 0; j < 26; j++) {
            val[i][j] = Rand() % P;
        }
    }
    g1.calc(), g2.calc();
    g1.confirm(), g2.confirm();
    for (int i = 1; i <= len; i++) {
        if (g1.get(i) != g2.get(i)) {
            for (int k = 1; k <= i; k++) {
                for (int j = 0; j < 26; j++) {
                    g1.test(j), g2.test(j);
                    if (g1.get(i - k) != g2.get(i - k)) {
                        g1.confirm(), g2.confirm();
                        ans[k] = 'a' + j;
                        break;
                    }
                }
            }
            printf("%s\n", ans + 1);
            return 0;
        }
    }
    printf("Same\n");
    return 0;
}