Diverta City

发布时间 2023-07-20 18:34:56作者: Ender_32k

题目中说 \(n\) 个点的无向完全图有 \(\frac{n!}{2}\) 条哈密顿路径。如果我们考虑方向(即每条路径钦定起点终点)的话,一共就有 \(n!\) 条了,因为一条路径 \((u,v)\) 可以钦定 \(u\to v\)\(v\to u\)。为了方便符号表达,以下都按照哈密顿路径数为 \(n!\) 即路径有向进行分析。

忘掉有关排列的东西,先冷静思考一下,\(n\) 个点的无向完全图的哈密顿路径数为啥是 \(n!\)

事实上,可以考虑归纳证明。

  • \(n=2\) 时,哈密顿路径数为 \(2\)\(1\to 2\) 或者 \(2\to 1\))。显然满足。
  • \(n=k\) 时,若哈密顿路径数为 \(k!\),下证 \(n=k+1\) 时,哈密顿路径数为 \((k+1)!\)

考虑 \(k\) 个点的完全图,我们在其中插入一个点 \(k+1\)。随便拉出原图的一个哈密顿路径,这样的路径有 \(k!\) 种,假设为 \(P:\{u\to v\}\),考虑新生成哈密顿路径 \(P'\) 的方式(记住这些方式,下面构造的证明要用到):

  • \(k+1\) 可以插到 \(v\) 后面变成路径的末尾,即 \(P':\{u\to v\to k+1\}\)
  • \(k+1\) 可以插到 \(u\) 前面变成路径的首位,即 \(P':\{k+1\to u\to v\}\)
  • 找到路径中间的一条边 \(w:i\to j\),这样的边有 \(k-1\) 条,我们把这条边从 \(P\) 中删去,用 \(i\to k+1\)\(k+1\to j\) 替换。即 \(P':\{u\to k+1\to v\}\)

根据一条原有的路径新生成哈密顿路径的方案数为 \(1+1+(k-1)=k+1\) 种,那么新生成哈密顿路径的条数就是 \(k!\cdot (k+1)=(k+1)!\)。归纳得证。

然而这个结论对这道题并没有什么用这启示我们归纳构造。显然 \(n=2\) 时我们直接将 \(1\)\(2\) 之间连权值为 \(1\) 的边即可。考虑 \(n=k\) 时我们构造出了合法的完全图,考虑插入 \(k+1\) 点:

将此时边权和最大的哈密顿路径的边权和记为 \(M\),假如我们现在构造出一个序列 \(a\) 满足:

  • \(\forall 1\le i<j\le k,a_i\neq a_j\)
  • \(\forall 1\le i<j\le k,1\le x<y\le k,(i,j)\neq (x,y),a_i+a_j\neq a_x+a_y\)
  • \(\forall 1\le i<j\le k,1\le x\le n,a_x\neq a_i+a_j\)

那么我们就可以将 \(k+1\)\(1\le i\le k\)\(i\) 连权值为 \((M+1)a_i\) 的边。我们把这样的边叫做新边

这样的图是一定合法的,因为考虑一条新的哈密顿路径仅会经过新的边 \(1\)\(2\),而由于 \(M\) 的最大性,属于旧哈密顿路径的边边权和不超过 \(M\),而 \(a\)任意至多 \(2\) 个数的和都至少差 \(1\),那么:

  • 如果两条新的哈密顿路径经过的新边相同,则它们属于原图的部分的边权和不同(这是因为由一开始的证明可知,如果经过两条新边的话,被新边“替换”的边和这个部分在原图形成哈密顿路径;如果经过一条的话,则原图内的部分就是原图的哈密顿路径),所以新的边权和也不同。
  • 如果两条新的哈密顿路径经过的新边不同,它们属于原图部分的边权和 \(\le M\),由于任意至多 \(2\) 条新边边权之和的两两差至少为 \(M+1\)\(a\) 中任意至多 \(2\) 个数的和至少差 \(1\)),所以新的边权和也不同。

所以问题来到如何构造一个满足条件的序列 \(a\),随便搜一下即可。构造出的 \(a\) 形如 \(\{1,2,4,7,12,20,29,38,52,73\}\)

然后我们需要求一张完全图的哈密顿路径边权最大和,也是随便搜一下即可。构造出来 \(n=10\) 时最大的哈密顿路径边权和 \(M\) 大概是 \(9.68\times 10^{10}\),题目 \(10^{11}\) 的限制还是挺充裕的。

const int N = 20;
int n, w[N][N], p[N], mx;
int a[N] = { 0, 1, 2, 4, 7, 12, 20, 29, 38, 52, 73 };

void gmx(int n) {
    mx = 0;
    for (int i = 1; i <= n; i++) p[i] = i;
    do {
        int res = 0;
        for (int i = 2; i <= n; i++)
            res += w[p[i]][p[i - 1]];
        mx = max(mx, res);
    } while (next_permutation(p + 1, p + n + 1));
}

bool chk(int n, int i) {
    p[n] = i;
    map<int, int> t;
    for (int i = 1; i <= n; i++) 
        if (t.count(p[i])) return 0;
        else t[p[i]] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j < i; j++)
            if (t.count(p[i] + p[j])) return 0;
            else t[p[i] + p[j]] = 1;
    return 1;
}

void dfs(int dep) {// Construct a[]
    if (dep == 11) {
        for (int i = 1; i <= 10; i++) 
            wr(p[i]), pc(' ');
        exit(0);
    }
    for (int i = 1; ; i++) {
        if (!chk(dep, i)) continue;
        p[dep] = i;
        dfs(dep + 1);
    }
}

signed main() {
    // dfs(1);
    n = rd();
    w[1][2] = w[2][1] = 1;
    for (int i = 3; i <= n; i++) {
        gmx(i - 1);
        for (int j = 1; j < i; j++)
            w[i][j] = w[j][i] = a[j] * (mx + 1);
    }
    for (int i = 1; i <= n; i++, puts(""))
        for (int j = 1; j <= n; j++)
            wr(w[i][j]), pc(' ');
    return 0;
}