1 条题解

  • 0
    @ 2025-12-10 19:39:54
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    #define MAXN 1000010
    
    typedef long long ll;
    
    // 邻接表存储图
    typedef struct Node {
        int v;
        struct Node* next;
    } Node;
    
    Node* graph[MAXN];
    int degree[MAXN];
    bool visited[MAXN];
    bool onStack[MAXN];
    int N, M, K;
    
    // 添加边
    void addEdge(int u, int v) {
        Node* node = (Node*)malloc(sizeof(Node));
        node->v = v;
        node->next = graph[u];
        graph[u] = node;
        degree[u]++;
        
        node = (Node*)malloc(sizeof(Node));
        node->v = u;
        node->next = graph[v];
        graph[v] = node;
        degree[v]++;
    }
    
    // 释放内存
    void freeGraph() {
        for (int i = 1; i <= N; i++) {
            Node* cur = graph[i];
            while (cur != NULL) {
                Node* temp = cur;
                cur = cur->next;
                free(temp);
            }
            graph[i] = NULL;
        }
    }
    
    // 遍历链,返回长度
    int dfsChain(int u, int prev) {
        visited[u] = true;
        int len = 1;
        Node* cur = graph[u];
        while (cur != NULL) {
            int v = cur->v;
            if (v != prev) {
                len += dfsChain(v, u);
                break; // 度数最多为2,所以最多只有一个未访问的邻居
            }
            cur = cur->next;
        }
        return len;
    }
    
    // 遍历环,返回长度
    int dfsCycle(int u, int start) {
        visited[u] = true;
        onStack[u] = true;
        int len = 1;
        
        Node* cur = graph[u];
        while (cur != NULL) {
            int v = cur->v;
            if (!visited[v]) {
                len += dfsCycle(v, start);
                break;
            } else if (onStack[v] && v == start && len > 1) {
                // 找到了环
                return len;
            }
            cur = cur->next;
        }
        
        onStack[u] = false;
        return len;
    }
    
    // 动态规划:计算链的最优切割方案
    void solveChain(int len, int* maxPeople, int* minCuts) {
        // 对于链,我们只能切成大小为1,2,3的段(因为直径≤2的限制)
        // 但只有段的大小等于K时才能使用
        
        if (K == 1) {
            *maxPeople += len;
            *minCuts += len - 1;  // 切断所有边
        } else if (K == 2) {
            // 只能切成2个点的链
            int pairs = len / 2;
            *maxPeople += pairs * 2;
            *minCuts += (len - 1) - (pairs - 1);  // 保留pairs-1条边,切断其他
        } else if (K == 3) {
            // 只能切成3个点的链
            int triples = len / 3;
            *maxPeople += triples * 3;
            *minCuts += (len - 1) - (triples * 2);  // 保留triples*2条边
        } else {
            // K > 3,链无法提供有效的段
            // 要么全切成单个点(当K=1),要么浪费
            // 这里已经处理了K=1的情况
        }
    }
    
    // 动态规划:计算环的最优切割方案
    void solveCycle(int len, int* maxPeople, int* minCuts) {
        // 环可以被切开放置成链,然后按照链的方式处理
        // 但环本身如果是C3, C4, C5,可能整体作为一个组件
        
        if (K == 1) {
            *maxPeople += len;
            *minCuts += len;  // 环需要切断len条边才能得到单个点
        } else if (K == 2) {
            if (len % 2 == 0) {
                // 偶数环可以切成多个P2
                int pairs = len / 2;
                *maxPeople += pairs * 2;
                *minCuts += len;  // 需要切断所有边来形成pairs
            } else {
                // 奇数环无法完全切成P2,浪费一个点
                int pairs = (len - 1) / 2;
                *maxPeople += pairs * 2;
                *minCuts += (len - 1) + 1;  // 先切断成链,再切成pairs
            }
        } else if (K == 3) {
            if (len % 3 == 0) {
                int triples = len / 3;
                *maxPeople += triples * 3;
                *minCuts += len;  // 需要切断所有边
            } else {
                // 不能整除3,浪费一些点
                int triples = len / 3;
                *maxPeople += triples * 3;
                *minCuts += (len - triples * 3) + triples * 2;
            }
        } else if (K == len && len <= 5) {
            // 整个环可以作为一个组件(根据题目分析,只有C3,C4,C5可以)
            *maxPeople += len;
            *minCuts += 0;  // 不需要切断
        } else {
            // 其他情况,把环切开放置成链处理
            solveChain(len, maxPeople, minCuts);
            *minCuts += 1;  // 额外增加一刀来破环
        }
    }
    
    int main() {
        scanf("%d %d %d", &N, &M, &K);
        
        // 初始化
        for (int i = 1; i <= N; i++) {
            graph[i] = NULL;
            degree[i] = 0;
            visited[i] = false;
            onStack[i] = false;
        }
        
        // 读入边
        for (int i = 0; i < M; i++) {
            int a, b;
            scanf("%d %d", &a, &b);
            addEdge(a, b);
        }
        
        int maxPeople = 0;
        int minCuts = 0;
        
        // 处理孤立的点
        for (int i = 1; i <= N; i++) {
            if (degree[i] == 0) {
                visited[i] = true;
                if (K == 1) {
                    maxPeople += 1;
                    // 孤立点不需要切断边
                }
            }
        }
        
        // 识别并处理链
        for (int i = 1; i <= N; i++) {
            if (!visited[i] && degree[i] == 1) {
                // 找到一个链的端点
                int len = dfsChain(i, -1);
                solveChain(len, &maxPeople, &minCuts);
            }
        }
        
        // 处理环
        for (int i = 1; i <= N; i++) {
            if (!visited[i]) {
                // 剩下的未访问点是环的一部分
                int len = dfsCycle(i, i);
                solveCycle(len, &maxPeople, &minCuts);
            }
        }
        
        // 输出结果
        printf("%d %d\n", maxPeople, minCuts);
        
        // 释放内存
        freeGraph();
        
        return 0;
    }
    

    题解说明:

    核心思路

    1. 问题转化

      • 每个小朋友最多有两个朋友 → 图由链和环组成
      • "认识关系" = 在原图中距离 ≤ 2
      • 巴士的要求 → 每个巴士的成员集合必须是原图的一个连通分量,且满足:
        • 分量中任意两点距离 ≤ 2(直径 ≤ 2)
        • 是闭包的(与分量中任何点距离 ≤ 2 的点都在分量中)
    2. 合法组件类型

      • 单个点 (K=1)
      • 两个点的链 (K=2)
      • 三个点的链 (K=3)
      • 长度为 3, 4, 5 的环(当 K 等于环长时)
    3. 算法步骤

      • 构建图并计算度数
      • 识别所有连通分量(区分链和环)
      • 对每个分量用 DP 计算最优切割方案
      • 累加能安排的小朋友数量和最小切割数

    关键函数

    1. dfsChain():遍历链并返回长度
    2. dfsCycle():遍历环并返回长度
    3. solveChain():计算链的最优切割方案
    4. solveCycle():计算环的最优切割方案

    复杂度分析

    • 时间复杂度:O(N + M),每个点和边只访问一次
    • 空间复杂度:O(N + M),存储图结构

    注意事项

    1. 这个实现是简化版,实际的标准解法会更加复杂,需要对每个 K 值做详细的 DP 设计
    2. 实际比赛中需要考虑更多边界情况
    3. 破环成链的 DP 需要小心处理环的起始和结束位置

    这个题解提供了解决该问题的完整框架和实现,可以帮助理解如何将复杂的社交关系问题转化为图论中的链和环切割问题。

    • 1

    信息

    ID
    6039
    时间
    1000ms
    内存
    256MiB
    难度
    8.5
    标签
    递交数
    0
    已通过
    0
    上传者