【Codeforces 1137C】Museums Tour

时间:2023-03-10 01:34:51
【Codeforces 1137C】Museums Tour
Codeforces 1137 C

题意:给一个有向图,一周有\(d\)天,每一个点在每一周的某些时刻会开放,现在可以在这个图上从\(1\)号点开始随意地走,问最多能走到多少个开放的点。一个点如果重复走到了很多次,只算一次。

思路:这空间太难卡了。。。

我们首先考虑将这个图的所有点都拆成\(d\)个,变成\(n\times d\)个点(为下文卡空间埋下伏笔),

即将\(i\)变成\((i,j)\),其中\(j\)表示到\(i\)的时候是一周的第\(j\)天(这里天数是\(0-start\),即一周的第一天是\(j=0\))

然后将每一条边\(u\rightarrow v\)都变成\((u,i)\rightarrow(v,(i+1)\ mod\ d)\),那么我们就会发现我们要求的就是\((0,0)\)开始我们最多能跑到多少不同的\((i,\star)\)。

所以我们将这个新图强连通分解,对于每一个强连通分量求出这个连通分量中不同的原来点的个数。

为了避免重复计算,我们证明以下结论:

在新图的一条链中出现的所有相同原图节点的节点们必定在同一个新图的强连通分量内。

证明:

首先我们假设这条链中有两个节点\((u,i)\)和\((u,j)\),那么原图上肯定有一个包含\(u\)的环,其长度\(mod\ d\)为\(j-i\),那我们从\((u,j)\)再走\(d-1\)次这个环,肯定回到\((u,i)\)。所以它们两个节点肯定在一个强连通分量内。证毕。

然后我们考虑\(dp(i)\)表示现在走到了第\(i\)个强连通分量,最多能够走到的点的个数。

那么转移就是枚举\(i\rightarrow j\),从\(dp(j)\)转移。

但是,只是这样的话肯定会\(mle\),我们需要卡空间。

我的做法是把所有的关于\(stl\)、递归的东西统统变成手写的,然后就变成了一堆奇奇怪怪的东西:

邻接表我用的是\(vector\),那么不要了!变成前向星!

强连通分解我用的是\(kosaraju\),那么这两个\(dfs\)都不要了!变成手写的递归!

\(dp\)的时候我用的是记忆化搜索,那么这个\(dfs\)也不要了!变成拓扑排序后\(bfs\)!(但是最后还是变成了手写递归-_-

关于手写递归:

首先我们要知道白点、灰点、黑点这三个概念:白点是没有访问过的点,灰点是正在访问的点,黑点是已经访问过了的点。它们分别对应了\(vis\)中的\(0\)、\(1\)、\(2\)。

然后我们手动维护一个调用栈\(stk\),那么我们的\(dfs\)应该是这样的:

// 起始点: s
stk.push(s);
while (!stk.empty()) {
    int u = stk.top(); stk.pop();
    if (vis[u] == 2) continue;
    if (vis[u] == 1) {
        // 做正常dfs的时候访问完儿子们后做的事
        vis[u] = 2;
        continue;
    }
    for (int v : g[u]) {
        if (!vis[v]) stk.push(v);
    }
}

十分不优美,但为了卡空间也没什么办法。。。

\(stk\)千万不要用\(std::stack\),肯定会\(tle\)。。。

老老实实手写,如果感觉空间不够也不要用\(vector\)。大胆开不要怂小心地算一个上界开大一点点的(比如加一个微不足道的常数\(10^5\))