题解 P1477 【[NOI2008]假面舞会】

· · 题解

(UPD 2021/08/21:感谢 @wxy_ 同学指出原来题解中的一些错误之处,已经对相关部分进行了修正。)

如果整个图没有环,且不存在两条共用起点和终点的相交链,显然最多能分的种类数是每个连通分量内最长链的长度之和。

如果整个图是由若干个不相交的环构成的话,最多能分的种类数是所有环长度的最大公约数(找环的时候,可以从连通块内的任意一点开始编号,第二次经过一个点的时候,它第二次的编号减去第一次的编号就是环的大小)。

除了这两种特殊情况之外,还有两种情况:

  1. 两个环之间有公共部分(指至少共用两个点)。
  2. 存在两个链共用起点和终点。

对于情况 1,合法的面具数一定是这两个环长度的公约数。

对于情况 2,合法的面具数一定是两个链长度差的约数。

我们将每个部分的结果合并,最后就可以得到整个图的结果。

先处理情况 1。我们考虑倒推:建图的时候不仅连一条 u \to v,边权为 1 的边之外,同时连一条 v \to u,边权为 -1 的边(这种连边方式可以确保我们从任意一个点开始,都可以遍历整个连通块)。

对于每个连通块,我们还是任意选一个点开始编号,经过一条边的时候将编号加上边权,和上面一样,第二次经过同一个点的时候,它第二次的编号减去第一次的编号就是环的大小。

接下来处理情况 2。我们发现,上面的建图方式已经很好地处理了这种情况(走反边的时候权值 -1,刚好可以得到两条链长度的差值),因此不需要再另外进行处理。

(最后别忘了题目要求面具最少要有三种)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#define INF 1e9
using namespace std;
struct edge {
  int v, w;
  bool operator<(const edge& a) const {
    return v < a.v || (v == a.v && w < a.w);
  }
};
vector<edge> e[100005];
int dis[100005], vis[100005], ans, res, cnt, maxv, minv;
int gcd(int x, int y) { return y == 0 ? x : gcd(y, x % y); }
void dfs(int u, int d) {
  if (dis[u]) {
    ans = gcd(ans, abs(d - dis[u]));
    return;
  }
  dis[u] = d, vis[u] = 1;
  maxv = max(maxv, dis[u]);
  minv = min(minv, dis[u]);
  for (auto i : e[u]) dfs(i.v, d + i.w);
}
int main() {
  int n, m;
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= m; i++) {
    int u, v;
    scanf("%d%d", &u, &v);
    e[u].push_back({v, 1});
    e[v].push_back({u, -1});
  }
  for (int i = 1; i <= n; i++)
    if (!vis[i]) {
      maxv = -INF, minv = INF;
      dfs(i, 1);
      res += maxv - minv + 1;
    }
  if (ans) {
    if (ans < 3)
      puts("-1 -1");
    else
      for (int i = 3; i <= ans; i++)
        if (ans % i == 0) {
          printf("%d %d\n", ans, i);
          break;
        }
  } else {
    if (res < 3)
      puts("-1 -1");
    else
      printf("%d 3\n", res);
  }
  return 0;
}