Description

给定一个仅包含 a~h 的字符串。

有一个 $n$​​​​ 个结点的无向图,编号为 $0$​​​​​ 到 $n−1$​​​​​​ 结点 $i$​​​ 与结点 $j$​​ 间有边相连当且仅当 $|i-j|=1$​ 或 $S_i=S_j$。

求这个无向图的直径和有多少对点间的最短距离与直径相同。

$n\leq10^5$

Solution

难是真的难,我看题解才懂,然后写代码时候挂了,又看了代码才过。。。

是个状压的妙用,思维难度不小。

先一步一步来。

很显然一个字母的边最多走一遍(因为走多遍和走一遍效果是一样的,而且不优)。

那么发现直径最大是 $15$,即 aabbccddeeffgghh 这个字符串,证明就用上面这个结论就行了,很显然。

然后考虑两点间最短路 $dis(i,j)$​。

下面记 a~h 为 $8$ 种颜色。分别为 $c_0\sim c_7$,$f(i,c)$ 表示 第 $i$ 个点到 $c$​ 颜色 的最短路长度,$T$ 为总颜色数。

想一想之后能得到:$dis(i,j)=\min(|i-j|,\min\limits_{0\leq k\leq7}\{f(i,k)+f(j,k)+1\})$​​。挺显然的,画个图或者想一想就明白了。

$f$​ 数组可以枚举颜色然后 BFS 得到,$O(Tn)$,然后最短路直接暴力枚举是 $O(Tn^2)$​ 的,得优化,然后我就不会了。

接下来就是看题解才会的东西了()

看上面那个式子,对于每个点 $i$,在链上往前走 $15$ 个点,这些点到 $i$ 的最短路可能是全在链上,也可能不在,而再往前,就一定要走相同字母连出来的边了。

于是对于这 $15$ 个点,暴力算一下最短路。

对于剩下的点,$dis(i,j)=\min\limits_{0\leq k\leq7}\{f(i,k)+f(j,k)+1\}$​。我们枚举点 $i$​ 和颜色 $k$​,需要快速得到前面所有点 $f(j,k)$ 的值以及个数。

不知道怎么做了,回头看一看有没有什么性质。

然后就定义 $g(a,b)$​​​ 表示从颜色 $a$​​​ 到颜色 $b$​​​ 的最短路长度。

发现 $g(c_i,c_j)\leq f(i,j)\leq g(c_i,c_j)+1$​​。这个很显然,要么是直接走过去,要么先走到最近的,再走一条相同字符的边。

变个形,$f(i,j)-g(c_i,c_j)=0/1$。

那么我们发现每个点对后面答案的贡献只与其颜色和上面的 $f(i,j)$​​ 是两种情况的某一种有关,那么可以把每种颜色的状态作为状态压缩一下,记录每种颜色和这个状态的个数,在每个点枚举所有颜色和状态,就能知道前面所有点 $f(j,k)$​​ 的值对应的个数了。

这个 $g$ 也在 BFS 时候顺便求一下就行。

计算一下复杂度,预处理 $O(Tn)$,后面是 $O(T^22^Tn)$,算一下,发现达到了 $10^9$ 级别。2s 时限的话也许能过(?)。

我直接莽完交的时候最大的点跑了 $\text{1996ms}$,总时长 $\text{1.11min}$​,真尬住了。。

后来加了个剪枝,快多了,最大的点只需要 $\text{468ms}$​。(见代码)

似乎有点抽象,上代码()

Code

有一些细节,颜色和点别弄混了。。。下标套下标有时候容易出错。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using std::cout;
using std::endl;

const int maxn = 1e5 + 10, maxt = 8, maxs = 1 << maxt;

char s[maxn];
int f[maxn][maxt + 1], g[maxt][maxt], cnt[maxt][maxs + 10];
int n, t = 8, used[maxt], a[maxn];
std::vector<int> p[maxt];

bool visi[maxn], visc[maxt];

int ans = 0;
long long tot = 0;

inline void updmi(int &a, int b) { a = std::min(a, b); }
inline void updans(int dis, int cnt)
{
    if (cnt == 0)
        return;
    if (dis == ans)
        tot += cnt;
    else if (dis > ans)
        ans = dis, tot = cnt;
}

int main()
{
    scanf("%d%s", &n, s + 1);
    for (int i = 1; i <= n; i++)
        a[i] = s[i] - 'a', p[a[i]].push_back(i);
    memset(f, 1, sizeof(f)), memset(g, 1, sizeof(g));
    std::queue<int> q;
    for (int i = 0; i < t; i++)
    {
        memset(visi, 0, sizeof(visi)), memset(visc, 0, sizeof(visc));
        g[i][i] = 0, visc[i] = 1;
        for (int v : p[i])
            q.push(v), visi[v] = 1, f[v][i] = 0;
        while (!q.empty())
        {
            int now = q.front();
            q.pop();
            if (now > 1 && !visi[now - 1])
                f[now - 1][i] = f[now][i] + 1, visi[now - 1] = true, q.push(now - 1);
            if (now < n && !visi[now + 1])
                f[now + 1][i] = f[now][i] + 1, visi[now + 1] = true, q.push(now + 1);
            if (!visc[a[now]])
            {
                visc[a[now]] = 1, g[a[now]][i] = f[now][i];
                for (int v : p[a[now]])
                    if (!visi[v])
                        f[v][i] = f[now][i] + 1, visi[v] = true, q.push(v);
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = std::max(1, i - 15); j < i; j++)
        {
            int dis = i - j;
            for (int c = 0; c < t; c++)
                updmi(dis, f[j][c] + f[i][c] + 1);
            updans(dis, 1);
        }
        if (i > 16)
        {
            int nowst = 0;
            for (int j = 0; j < t; j++)
                nowst = (nowst << 1) | (f[i - 16][j] - g[a[i - 16]][j]);
            cnt[a[i - 16]][nowst]++;
            for (int j = 0; j < t; j++)
            {
                for (int k = 0; k < (1 << t); k++)
                {
                    if (cnt[j][k] == 0) // 此处剪枝优化是很大的。
                        continue;
                    int dis = 1 << 30;
                    for (int q = 0; q < t; q++)
                        updmi(dis, f[i][q] + g[q][j] + ((k >> (t - q - 1)) & 1) + 1);
                    updans(dis, cnt[j][k]);
                }
            }
        }
    }
    printf("%d %lld\n", ans, tot);
}

一点闲话

谁知道如何能想到这个 $g$ 啊,我想不到这个点。。