题解:BISHI38 有向二分图构造

题目链接

有向二分图构造

题目描述

给定一张有向图,含 个点与 条有向边。需要给每个点染成黑或白两色。若一条边的起点为黑色、终点为白色,则称其为“核心边”。请构造一种染色方案,使得核心边的条数不少于 ,并输出你构造方案下所有核心边的编号(按输入顺序从 编号)。保证至少存在一种可行解。

解题思路

随机将每个点独立地以 概率染黑 / 染白时,任意一条边成为“黑 白”的概率是 ,因此核心边条数的期望为 ,从而存在一份染色使核心边不少于 。我们用“条件期望法”进行完全去随机化,线性时间即可构造:

  • 设当前已为部分点定色,其余点未定。对一个未定点
    • 若将其染黑,则它指向的“已定为白”的出边全部计入答案;指向“未定”点的每条出边以 的期望贡献记入;
    • 若将其染白,则指向它的“已定为黑”的入边全部计入答案;来自“未定”点的每条入边以 的期望贡献记入。
  • 我们在两种选择中取期望贡献较大的那一种,并据此把点 定色;再把相关邻接点的统计量更新。如此按任意顺序依次处理所有点,整体期望不下降,最终得到的确定染色使核心边数不少于

实现要点:

  • 需要维护每个未定点 的四个计数:(指向未定点的出边数)、(指向已定白点的出边数)、(来自未定点的入边数)、(来自已定黑点的入边数)。
  • 比较 ,前者大就染黑,否则染白。
  • 所有点定色后,再线性扫描所有边,输出起点黑且终点白的边编号。

代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T; 
    if (!(cin >> T)) return 0;
    while (T--) {
        int n, m; 
        cin >> n >> m;
        vector<pair<int,int>> edges(m+1);
        vector<vector<int>> outG(n+1), inG(n+1);
        for (int i = 1; i <= m; ++i) {
            int u, v; cin >> u >> v;
            edges[i] = {u, v};
            outG[u].push_back(v);
            inG[v].push_back(u);
        }

        vector<int> color(n+1, -1), decided(n+1, 0);
        vector<int> outUndec(n+1), inUndec(n+1), outWhite(n+1, 0), inBlack(n+1, 0);
        for (int i = 1; i <= n; ++i) {
            outUndec[i] = (int)outG[i].size();
            inUndec[i] = (int)inG[i].size();
        }

        for (int i = 1; i <= n; ++i) {
            long long sBlack = 2LL * outWhite[i] + outUndec[i];
            long long sWhite = 2LL * inBlack[i] + inUndec[i];
            if (sBlack >= sWhite) {
                color[i] = 0; // 黑
            } else {
                color[i] = 1; // 白
            }
            decided[i] = 1;

            // 更新与 i 相邻的未定点统计
            if (color[i] == 0) {
                // i 为黑:其出边使目标点的 inBlack 增加
                for (int v : outG[i]) if (!decided[v]) {
                    --inUndec[v];
                    ++inBlack[v];
                }
                for (int u : inG[i]) if (!decided[u]) {
                    --outUndec[u];
                }
            } else {
                // i 为白:其入边使源点的 outWhite 增加
                for (int v : outG[i]) if (!decided[v]) {
                    --inUndec[v];
                }
                for (int u : inG[i]) if (!decided[u]) {
                    --outUndec[u];
                    ++outWhite[u];
                }
            }
        }

        vector<int> ans;
        ans.reserve(m);
        for (int i = 1; i <= m; ++i) {
            int u = edges[i].first, v = edges[i].second;
            if (color[u] == 0 && color[v] == 1) ans.push_back(i);
        }
        cout << (int)ans.size() << '\n';
        for (int i = 0; i < (int)ans.size(); ++i) {
            if (i) cout << ' ';
            cout << ans[i];
        }
        cout << '\n';
    }
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
    static class FastScanner {
        private final InputStream in;
        private final byte[] buffer = new byte[1 << 16];
        private int ptr = 0, len = 0;
        FastScanner(InputStream is) { this.in = is; }
        private int read() throws IOException {
            if (ptr >= len) {
                len = in.read(buffer);
                ptr = 0;
                if (len <= 0) return -1;
            }
            return buffer[ptr++];
        }
        long nextLong() throws IOException {
            int c; long sgn = 1, x = 0;
            do { c = read(); } while (c <= 32);
            if (c == '-') { sgn = -1; c = read(); }
            while (c > 32) { x = x * 10 + (c - '0'); c = read(); }
            return x * sgn;
        }
        int nextInt() throws IOException { return (int) nextLong(); }
    }

    public static void main(String[] args) throws Exception {
        FastScanner fs = new FastScanner(System.in);
        StringBuilder sb = new StringBuilder();
        int T = fs.nextInt();
        while (T-- > 0) {
            int n = fs.nextInt();
            int m = fs.nextInt();
            int[] eu = new int[m + 1];
            int[] ev = new int[m + 1];
            List<Integer>[] outG = new ArrayList[n + 1];
            List<Integer>[] inG = new ArrayList[n + 1];
            for (int i = 1; i <= n; i++) { outG[i] = new ArrayList<>(); inG[i] = new ArrayList<>(); }
            for (int i = 1; i <= m; i++) {
                int u = fs.nextInt();
                int v = fs.nextInt();
                eu[i] = u; ev[i] = v;
                outG[u].add(v);
                inG[v].add(u);
            }

            int[] color = new int[n + 1];
            int[] decided = new int[n + 1];
            Arrays.fill(color, -1);
            int[] outUndec = new int[n + 1];
            int[] inUndec = new int[n + 1];
            int[] outWhite = new int[n + 1];
            int[] inBlack = new int[n + 1];
            for (int i = 1; i <= n; i++) {
                outUndec[i] = outG[i].size();
                inUndec[i] = inG[i].size();
            }

            for (int i = 1; i <= n; i++) {
                long sBlack = 2L * outWhite[i] + outUndec[i];
                long sWhite = 2L * inBlack[i] + inUndec[i];
                color[i] = (sBlack >= sWhite) ? 0 : 1;
                decided[i] = 1;
                if (color[i] == 0) {
                    for (int v : outG[i]) if (decided[v] == 0) { inUndec[v]--; inBlack[v]++; }
                    for (int u : inG[i]) if (decided[u] == 0) { outUndec[u]--; }
                } else {
                    for (int v : outG[i]) if (decided[v] == 0) { inUndec[v]--; }
                    for (int u : inG[i]) if (decided[u] == 0) { outUndec[u]--; outWhite[u]++; }
                }
            }

            int cnt = 0;
            for (int i = 1; i <= m; i++) if (color[eu[i]] == 0 && color[ev[i]] == 1) cnt++;
            sb.append(cnt).append('\n');
            int printed = 0;
            for (int i = 1; i <= m; i++) {
                if (color[eu[i]] == 0 && color[ev[i]] == 1) {
                    if (printed++ > 0) sb.append(' ');
                    sb.append(i);
                }
            }
            sb.append('\n');
        }
        System.out.print(sb.toString());
    }
}
import sys

def solve():
    data = sys.stdin.buffer.read().split()
    it = iter(data)
    T = int(next(it))
    out_lines = []
    for _ in range(T):
        n = int(next(it)); m = int(next(it))
        eu = [0]*(m+1); ev = [0]*(m+1)
        outG = [[] for _ in range(n+1)]
        inG = [[] for _ in range(n+1)]
        for i in range(1, m+1):
            u = int(next(it)); v = int(next(it))
            eu[i] = u; ev[i] = v
            outG[u].append(v)
            inG[v].append(u)
        color = [-1]*(n+1)
        decided = [0]*(n+1)
        outUndec = [0]*(n+1)
        inUndec = [0]*(n+1)
        outWhite = [0]*(n+1)
        inBlack = [0]*(n+1)
        for i in range(1, n+1):
            outUndec[i] = len(outG[i])
            inUndec[i] = len(inG[i])
        for i in range(1, n+1):
            sBlack = 2*outWhite[i] + outUndec[i]
            sWhite = 2*inBlack[i] + inUndec[i]
            color[i] = 0 if sBlack >= sWhite else 1
            decided[i] = 1
            if color[i] == 0:
                for v in outG[i]:
                    if decided[v] == 0:
                        inUndec[v] -= 1
                        inBlack[v] += 1
                for u in inG[i]:
                    if decided[u] == 0:
                        outUndec[u] -= 1
            else:
                for v in outG[i]:
                    if decided[v] == 0:
                        inUndec[v] -= 1
                for u in inG[i]:
                    if decided[u] == 0:
                        outUndec[u] -= 1
                        outWhite[u] += 1
        ans = [str(i) for i in range(1, m+1) if color[eu[i]] == 0 and color[ev[i]] == 1]
        out_lines.append(str(len(ans)))
        out_lines.append(' '.join(ans))
    sys.stdout.write('\n'.join(out_lines))

if __name__ == '__main__':
    solve()

算法及复杂度

  • 算法:条件期望法去随机化;顺序定点颜色并维护局部统计,最终核心边数不少于
  • 时间复杂度: 每组
  • 空间复杂度: