1 条题解

  • 1
    @ 2025-5-13 15:56:00

    题解:监狱囚犯交换问题

    这段代码解决的是一个关于二分图匹配的问题,具体来说是要找到两个监狱之间最大的囚犯交换数量,同时确保没有危险对的囚犯被交换到同一监狱。问题可以转化为在一个二分图中找到最大的独立集,使得交换后的囚犯集合不包含任何危险对。

    问题分析

    两所监狱各有m名囚犯,存在r对危险关系,每对包含一名第一监狱的囚犯和一名第二监狱的囚犯。目标是交换两监狱各k名囚犯,使得k尽可能大且不违反任何危险关系。

    代码详细解释

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define MAXN 210
    
    // 边结构
    struct LEdge{
        int ed, next;
    }edge[MAXN * MAXN];
    int head[2 * MAXN], nEdge;
    
    // 初始化图
    void init(){
        nEdge = 0;
        memset(head, 0xff, sizeof(head));
    }
    
    // 添加边
    void addEdge(int i, int j){
        edge[nEdge].ed = j;
        edge[nEdge].next = head[i];
        head[i] = nEdge++;
    }
    
    int f[MAXN][MAXN];  // DP数组,f[i][j]表示能否从原监狱选i人,从交换监狱选j人
    int vis[MAXN][2];   // 标记节点是否被访问
    
    int m, r;
    int np, nq;  // 记录DFS中两种类型节点的数量
    
    // DFS遍历图,计算连通块中两种类型节点的数量
    void dfs(int cur, int flag){
        int i;
        if (!flag) np++;  // 类型0节点计数
        else nq++;        // 类型1节点计数
        vis[cur][flag] = 1;
        
        // 遍历所有邻接边
        for (i = head[2 * cur + flag]; i != -1; i = edge[i].next){
            if (vis[edge[i].ed / 2][1 - flag]) continue;
            dfs(edge[i].ed / 2, 1 - flag);
        }
    }
    
    // 动态规划更新可能的选择
    void dp(int np, int nq){
        int j, k;
        // 逆序遍历,确保每个连通块只被考虑一次
        for (j = m / 2; j >= 0; j--){
            for (k = m / 2; k >= 0; k--){
                if (j - np >= 0 && k - nq >= 0){
                    f[j][k] |= f[j - np][k - nq];  // 可以从之前的状态转移过来
                }
            }
        }
    }
    
    int main(){
        int i, j, k, T;
        scanf("%d", &T);  // 读取测试用例数量
        
        while(T--){
            init();  // 初始化图
            scanf("%d%d", &m, &r);  // 读取囚犯数量和危险对数量
            
            // 构建图:添加危险对的边
            for (k = 0; k < r; k++){
                scanf("%d%d", &i, &j);
                addEdge(2 * i, 2 * j + 1);      // 第一监狱的i不能和第二监狱的j在同一组
                addEdge(2 * j + 1, 2 * i);      // 双向边
            }
            
            // 初始化访问标记和DP数组
            memset(vis, 0, sizeof(vis));
            memset(f, 0, sizeof(f));
            f[0][0] = 1;  // 初始状态:不选任何囚犯
            
            // 遍历所有节点,处理每个连通块
            for (i = 1; i <= m; i++){
                if (!vis[i][0]){
                    np = nq = 0;
                    dfs(i, 0);  // 从类型0节点开始DFS
                    dp(np, nq);  // 更新DP数组
                }
                if (!vis[i][1]){
                    np = nq = 0;
                    dfs(i, 1);  // 从类型1节点开始DFS
                    dp(np, nq);  // 更新DP数组
                }
                if (f[m / 2][m / 2]) break;  // 如果已经找到最大可能的k,提前退出
            }
            
            // 查找最大的k,使得f[k][k]为真
            for (i = m / 2; i >= 0; i--)
                if (f[i][i]) break;
            printf("%d\n", i);  // 输出结果
        }
        return 0;
    }
    

    复杂度分析

    • 时间复杂度:O(T * m^2),其中T是测试用例数量,m是囚犯数量。每个测试用例需要构建图、进行DFS分解和动态规划计算。
    • 空间复杂度:O(m^2),主要用于存储DP数组和图的邻接表。

    这个算法通过将问题转化为图的连通块分析和动态规划,巧妙地解决了囚犯交换的最大匹配问题,确保了在满足所有约束条件的情况下找到最优解。

    • 1

    信息

    ID
    637
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    3
    已通过
    1
    上传者