CF 1425B Blue and Red of Our Faculty!

容易发现这张图就是若干个相交于点 \(1\) 的环,我们先 \(dfs\) 把环找出来。

考虑最后什么情况会停下来:

  1. 外面有若干个环被走完,两个人在当前环内相遇。最后在同一个点或隔一条边均可。
  2. 走完了整个图。此时两个人均在 \(1\) 号点。
  3. 只有一条边没被走。此时一个人在 \(1\) 号点。

对于第一种情况,我们枚举相遇的环,设这个环的大小为 \(R\),两个人分别在外面走了 \(A,B\),那么两个人能够在这个环相遇的充要条件是:\(|A-B| \leq R - 2\)。否则一个人就会把这个环走完。我们再枚举一下 \(A\),那么可行的 \(B\) 就是一段区间,容易通过背包计算。

具体来说:我们设 \(f[i][j]\) 表示前 \(i\) 个环,\(A - B = j\) 的方案数,\(g[i][j]\) 表示后缀的背包。我们在统计答案的时候只需要对 \(g[i]\) 做一个前缀和即可。

对于第 \(2\) 种情况,我们直接对所有的环求一遍背包即可。

对于第 \(3\) 种情况,我们还是枚举最后剩下的那一条边在哪个环中,再枚举 \(A\),这样可行的 \(B\) 就确定了,直接合并前缀后缀两个背包即可。

需要注意的是,对于第 \(1\) 种情况,环是可以不选的,对于 \(2, 3\) 两种情况,环是必选的。对于 \(1, 3\) 两种情况,答案还需要 \(\times2\),因为不能确定 \(A\) 从哪个方向进去,而对于第 \(2\) 种情况,方向是无所谓的。

#include <bits/stdc++.h>

const int N = 8e3 + 20, Mod = 1e9 + 7;
#define tr(i) (i + 4005)

inline int read()
{
    int x = 0;
    char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return x;
}

int tot = 1, fir[N], nex[N], got[N];
inline void AddEdge(int u, int v)
{
    nex[++tot] = fir[u], fir[u] = tot, got[tot] = v;
}

int vis[N], a[N];
inline int dfs(int u)
{
    vis[u] = true;
    for (int i = fir[u]; i; i = nex[i])
        if (!vis[got[i]]) return dfs(got[i]) + 1;
    return 0;
}

int f[2][N], g[N >> 1][N];

int main()
{
    int n = read(), M = read(), m = 0; vis[1] = true;
    for (int i = 1; i <= M; i++)
    {
        int u = read(), v = read();
        AddEdge(u, v), AddEdge(v, u);
    }
    for (int i = fir[1]; i; i = nex[i]) 
        if (!vis[got[i]]) a[++m] = dfs(got[i]) + 2;
    f[0][tr(0)] = g[m + 1][tr(0)] = 1;
    int now = 0;
    for (int i = m; i >= 1; --i) for (int j = -M; j <= M; ++j)
    {
        g[i][tr(j)] = g[i + 1][tr(j)];
        if (j - a[i] >= -M) (g[i][tr(j)] += g[i + 1][tr(j - a[i])]) %= Mod;
        if (j + a[i] <=  M) (g[i][tr(j)] += g[i + 1][tr(j + a[i])]) %= Mod;
    }
    int ans = 0;
    for (int i = 1; i <= m; ++i)
    {
        int w = a[i] - 2, res = 0;
        for (int j = -M; j <= M; ++j) (g[i + 1][tr(j)] += g[i + 1][tr(j - 1)]) %= Mod;
        for (int j = -M; j <= M; ++j) if (f[now][tr(j)])
        {
            int l = std::max(j - w, -M), r = std::min(j + w, M);
            (res += 2LL * (g[i + 1][tr(r)] - g[i + 1][tr(l - 1)] + Mod) * f[now][tr(j)] % Mod) %= Mod;
        }
        (ans += res) %= Mod, now ^= 1;
        for (int j = -M; j <= M; ++j)
        {
            f[now][tr(j)] = f[now ^ 1][tr(j)];
            if (j - a[i] >= -M) (f[now][tr(j)] += f[now ^ 1][tr(j - a[i])]) %= Mod;
            if (j + a[i] <=  M) (f[now][tr(j)] += f[now ^ 1][tr(j + a[i])]) %= Mod;
        }
    }
    memset(g, 0, sizeof(g)), g[0][tr(0)] = 1;
    for (int i = 1; i <= m; ++i) for (int j = -M; j <= M; ++j)
    {
        if (j - a[i] >= -M) (g[i][tr(j)] += g[i - 1][tr(j - a[i])]) %= Mod;
        if (j + a[i] <=  M) (g[i][tr(j)] += g[i - 1][tr(j + a[i])]) %= Mod;
    }
    (ans += g[m][tr(0)]) %= Mod;
    memset(f, 0, sizeof(f)), f[0][tr(0)] = 1, now = 0;
    for (int i = m; i >= 1; --i)
    {
        for (int j = -M; j <= M; ++j)
        {
            if (!f[now][tr(j)]) continue;
            int w1 = - (a[i] - 1) - j, w2 = (a[i] - 1) - j;
            if (w1 >= -M) (ans += 2LL * f[now][tr(j)] * g[i - 1][tr(w1)] % Mod) %= Mod;
            if (w2 <=  M) (ans += 2LL * f[now][tr(j)] * g[i - 1][tr(w2)] % Mod) %= Mod;
        }
        now ^= 1;
        for (int j = -M; j <= M; ++j)
        {
            f[now][tr(j)] = 0;
            if (j - a[i] >= -M) (f[now][tr(j)] += f[now ^ 1][tr(j - a[i])]) %= Mod;
            if (j + a[i] <=  M) (f[now][tr(j)] += f[now ^ 1][tr(j + a[i])]) %= Mod;
        }
    }
    std::cout << ans << std::endl;
    return 0;
}
posted @ 2020-10-09 07:41  bo1949  阅读(114)  评论(0)    收藏  举报