北京省选集训2019 图的难题


2019-04-13

题目大意

给定一张无向图,要求把边染成黑白两色,要求所有白色边构成的子图没有环,且所有黑色边构成的子图没有环。

多组询问,输出是否有解。

\(n\le 500,m\le 2n,T\le 10\)

题解

显然,由白边构成的图是一个森林,黑边构成的图也是个森林。

显然森林所有的导出子图也是森林。

那么显然图是一个森林的充要条件为其最大导出子图的 \(|E|-|V|\le -1\)

我们考虑原问题,如果一张图 \(G\) 要有解,那么 \(G\) 的所有导出子图都要有解,且该图的 \(|E|\le 2(|V|-1)\),也就是让黑白两种颜色都能是一片森林。

有了这个有理有据的性质,我们就把问题转化成了:对于图 \(G\),定义 \(\rho_G=|E|-2|V|+2\),求出一张图所有导出子图 \(\rho?\) 的最大值。

我们考虑网络流,\(S?\) 向所有原图的边(看作点)连边,容量为 \(1?\),原图中每条边向与之相连的点连边,容量 \(\infty?\),原图中点向 \(T?\) 连边,容量为 \(2?\)

考虑最小割,把割 \(S\to \text{原图中边}\) 的边看成是不选该边,把 \(原图中点\to T\) 的边看成是选该点,这样 \(\rho_G=|E|-\text{不被选的边}+\text{被选的点}+2=|E|-\text{最小割}+2\)

然后发现输出 "NO"\(\times \infty\),因为它把所有边全割了,这样其实就是选了一个空集,\(\rho=2\),GG。

我们强制选一个点,枚举一个点强行割掉,发现这样割使用了 \(2?\),正好答案就是 \(\rho_G=|E|-\text{最小割}?\)

直接判断正负即可。

复杂度 \(\mathcal{O}(\text{能过})?\)

代码

cpp
#include <bits/stdc++.h>

using namespace std;

// 读入输出优化直接删了

const int maxN = 505;
const int maxn = maxN << 2;
const int maxm = maxn << 3;
const int inf = 0x3f3f3f3f;

struct Edge
{
    int to, nxt, cap;
} e[maxm << 1];

int first[maxn], fb[maxn];

int cnt;
inline void add_edge(int from, int to, int cap)
{
    e[++cnt].nxt = fb[from];
    fb[from] = cnt;
    e[cnt].to = to;
    e[cnt].cap = cap;
    e[++cnt].nxt = fb[to];
    fb[to] = cnt;
    e[cnt].to = from;
    e[cnt].cap = 0;
}

int n, m, S, T;

struct EDGE
{
    int from, to;
} ee[maxn];

inline void jiantu(int k)
{
    memset(fb, 0xff, sizeof(fb));
    cnt = -1;
    for (int i = 1; i <= n; ++i)
        if (i != k)
            add_edge(i, T, 2);
    for (int i = 1; i <= m; ++i)
    {
        add_edge(i + n, ee[i].from, inf);
        add_edge(i + n, ee[i].to, inf);
        add_edge(S, i + n, 1);
    }
}

int dep[maxn];

inline bool bfs()
{
    memset(dep, 0x3f, sizeof(dep));
    for (int i = S; i <= T; ++i)
        first[i] = fb[i];
    queue<int> q;
    q.push(S);
    dep[S] = 0;
    while (!q.empty())
    {
        int now = q.front();
        q.pop();
        for (int i = first[now]; ~i; i = e[i].nxt)
        {
            int to = e[i].to;
            if (dep[to] >= inf && e[i].cap > 0)
            {
                q.push(to);
                dep[to] = dep[now] + 1;
            }
        }
    }
    return dep[T] < inf;
}

inline int dfs(int now, int limit)
{
    if (now == T || !limit)
        return limit;
    int flow = 0;
    for (int i = first[now]; ~i; i = e[i].nxt)
    {
        first[now] = i;
        int to = e[i].to, f;
        if (dep[to] == dep[now] + 1 && (f = dfs(to, min(limit, e[i].cap))))
        {
            e[i].cap -= f;
            e[i ^ 1].cap += f;
            limit -= f;
            flow += f;
            if (!limit)
                break;
        }
    }
    return flow;
}

inline int Dinic()
{
    int ans = 0;
    while (bfs())
        ans += dfs(S, inf);
    return ans;
}

inline void solve()
{
    read(n), read(m);
    S = 0;
    T = n + m + 1;
    for (int i = 1; i <= m; ++i)
        read(ee[i].from), read(ee[i].to);
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        jiantu(i);
        ans = max(ans, m - Dinic());
    }
    puts(ans > 0 ? "No" : "Yes");
}

int main()
{
    int TT;
    read(TT);
    while (TT--)
        solve();
    return 0;
}

Cite this post

@misc{pu2019beijing2019graph,
  author = {Pu, Fanyi},
  title  = {北京省选集训2019 图的难题},
  year   = {2019},
  month  = {4},
  url    = {https://pufanyi.com/blog/beijing-2019-graph}
}