云计算百科
云计算领域专业知识百科平台

UVa 11082 Matrix Decompressing

题目大意

给定一个 R×CR \\times CR×C 的正整数矩阵,已知它的 累积行和 与 累积列和 ,要求还原出原矩阵的所有 R×CR \\times CR×C 个元素。

具体来说:

  • RowSum(i)=∑j=1CAijRowSum(i) = \\sum_{j=1}^{C} A_{ij}RowSum(i)=j=1CAij
  • CumulativeRowSum(i)=∑k=1iRowSum(k)CumulativeRowSum(i) = \\sum_{k=1}^{i} RowSum(k)CumulativeRowSum(i)=k=1iRowSum(k)
  • ColumnSum(i)=∑j=1RAjiColumnSum(i) = \\sum_{j=1}^{R} A_{ji}ColumnSum(i)=j=1RAji
  • CumulativeColumnSum(i)=∑k=1iColumnSum(k)CumulativeColumnSum(i) = \\sum_{k=1}^{i} ColumnSum(k)CumulativeColumnSum(i)=k=1iColumnSum(k)

题目保证至少有一个解,并且规定矩阵中每个元素是 111202020 之间的整数。
若有多组解,输出任意一组即可。

输入格式

第一行是测试用例数 TTT
每个测试用例包含三行:

  • 两个整数 RRRCCC
  • RRR 个整数表示累积行和
  • CCC 个整数表示累积列和
  • 输出格式

    对每个测试用例,先输出一行 Matrix x,其中 xxx 是测试用例编号(从 1 开始)。
    接下来输出 RRR 行,每行 CCC 个整数表示矩阵元素。


    解题思路

    1. 转化为网络流模型

    我们已知的是累积和,可以先将其转化为行和与列和:

    设:
    ri=CumulativeRowSum(i)−CumulativeRowSum(i−1)cj=CumulativeColumnSum(j)−CumulativeColumnSum(j−1)
    r_i = CumulativeRowSum(i) – CumulativeRowSum(i-1)
    \\\\
    c_j = CumulativeColumnSum(j) – CumulativeColumnSum(j-1)
    ri=CumulativeRowSum(i)CumulativeRowSum(i1)cj=CumulativeColumnSum(j)CumulativeColumnSum(j1)

    其中 CumulativeRowSum(0)=0CumulativeRowSum(0) = 0CumulativeRowSum(0)=0CumulativeColumnSum(0)=0CumulativeColumnSum(0) = 0CumulativeColumnSum(0)=0

    那么 rir_iri 就是第 iii 行的和, cjc_jcj 就是第 jjj 列的和。

    问题转化为:构造一个 R×CR \\times CR×C 的正整数矩阵,使得第 iii 行的和为 rir_iri ,第 jjj 列的和为 cjc_jcj ,且每个元素在 [1,20][1, 20][1,20] 之间。

    2. 建立流量网络

    这是一个典型的 二分图匹配 问题,可以用 最大流 解决。

    构造一个网络:

    • 源点 SSS 连接 RRR 个行结点 RiR_iRi ,容量为 rir_iri
    • 列结点 CjC_jCj 连接汇点 TTT ,容量为 cjc_jcj
    • 每个行结点 RiR_iRi 连接每个列结点 CjC_jCj ,容量为 191919(因为元素最大是 202020,最小是 111,所以流量范围是 0 190~190 19,实际值===流量+1+1+1

    这样,从 SSSRiR_iRi 的流量表示第 iii 行的总分配量,从 RiR_iRiCjC_jCj 的流量表示 Aij−1A_{ij} – 1Aij1 ,从 CjC_jCjTTT 的流量表示第 jjj 列的总分配量。

    3. 流量与矩阵元素的关系

    fijf_{ij}fij 为边 Ri→CjR_i \\to C_jRiCj 的流量( 0≤fij≤190 \\le f_{ij} \\le 190fij19 ),则:
    Aij=fij+1
    A_{ij} = f_{ij} + 1
    Aij=fij+1

    因为 fij∈[0,19]f_{ij} \\in [0, 19]fij[0,19] ,所以 Aij∈[1,20]A_{ij} \\in [1, 20]Aij[1,20] ,满足题目要求。

    4. 流量约束

    行和约束:
    ∑j=1C(fij+1)=ri⇒∑j=1Cfij=ri−C
    \\sum_{j=1}^{C} (f_{ij} + 1) = r_i \\quad \\Rightarrow \\quad \\sum_{j=1}^{C} f_{ij} = r_i – C
    j=1C(fij+1)=rij=1Cfij=riC

    列和约束:
    ∑i=1R(fij+1)=cj⇒∑i=1Rfij=cj−R
    \\sum_{i=1}^{R} (f_{ij} + 1) = c_j \\quad \\Rightarrow \\quad \\sum_{i=1}^{R} f_{ij} = c_j – R
    i=1R(fij+1)=cji=1Rfij=cjR

    这样我们就把原问题转化为一个 带下界(最小为 000)和上界(最大为 191919)的流分配问题 ,可以用 Dinic\\texttt{Dinic}Dinic 算法 求解最大流。


    算法步骤

  • 读入 RRRCCC
  • 计算 rir_iricjc_jcj
  • 建图:
    • 源点 S=0S=0S=0 ,行结点 1∼R1 \\sim R1R ,列结点 R+1∼R+CR+1 \\sim R+CR+1R+C ,汇点 T=R+C+1T=R+C+1T=R+C+1
    • S→RiS \\to R_iSRi ,容量 ri−Cr_i – CriC
    • Cj→TC_j \\to TCjT ,容量 cj−Rc_j – RcjR
    • Ri→CjR_i \\to C_jRiCj ,容量 191919
    • 为了确保流量可行,还需添加辅助边 S→CjS \\to C_jSCj 容量 RRRRi→TR_i \\to TRiT 容量 CCC (这是常见的技巧,用于处理流量下界)
  • 跑最大流
  • 根据残余网络得到 fijf_{ij}fij ,计算 Aij=fij+1A_{ij} = f_{ij} + 1Aij=fij+1
  • 输出矩阵

  • 时间复杂度

    顶点数 V=R+C+2≤42V = R + C + 2 \\le 42V=R+C+242 ,边数 E=O(R×C)E = O(R \\times C)E=O(R×C)
    Dinic\\texttt{Dinic}Dinic 算法在二分图上复杂度为 O(VE)O(\\sqrt{V}E)O(VE) ,完全可行。


    参考代码

    // Matrix Decompressing
    // UVa ID: 11082
    // Verdict: Accepted
    // Submission Date: 2023-05-31
    // UVa Run Time: 0.000s
    //
    // 版权所有(C)2023,邱秋。metaphysis # yeah dot net

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

    const int INF = 0x3f3f3f3f; // 定义无穷大值

    // 网络流中的边结构体
    struct arc {
    int u, v; // 边的起点和终点
    int capacity; // 边的容量
    int residual; // 边的剩余容量
    int next; // 下一条边的索引(邻接表)
    };

    // Dinic 最大流算法类
    class Dinic {
    private:
    arc *arcs; // 存储所有边的数组
    int vertices; // 顶点总数
    int source, sink; // 源点和汇点
    int idx; // 当前边的索引
    int *head; // 每个顶点的第一条边索引(邻接表头)
    int *dist; // BFS 中的距离数组
    int *current; // 当前弧优化数组

    // BFS 分层:构建层次网络
    bool bfs() {
    memset(dist, 1, sizeof(int) * vertices); // 初始化所有距离为 -1
    queue<int> q;
    q.push(source);
    dist[source] = 1; // 源点距离设为 1(或 0,这里设为 1 避免与 -1 混淆)

    while (!q.empty()) {
    int u = q.front(); q.pop();
    if (u == sink) break; // 到达汇点

    // 遍历 u 的所有出边
    for (int i = head[u]; ~i; i = arcs[i].next)
    if (arcs[i].residual > 0 && dist[arcs[i].v] < 0) {
    // 如果还有剩余容量且目标顶点未访问
    dist[arcs[i].v] = dist[u] + 1;
    q.push(arcs[i].v);
    }
    }
    return dist[sink] > 0; // 返回是否能到达汇点
    }

    // DFS 寻找增广路
    int dfs(int u, int flow) {
    if (u == sink) return flow; // 到达汇点,返回流量

    // 当前弧优化:从上次检查到的边继续
    for (int &i = current[u]; ~i; i = arcs[i].next) {
    int v = arcs[i].v, r = arcs[i].residual;

    // 确保按层次网络前进且还有剩余容量
    if (dist[v] == (dist[u] + 1) && r > 0) {
    int volume = dfs(v, min(r, flow)); // 递归寻找增广路
    if (volume > 0) {
    // 更新边的剩余容量
    arcs[i].residual -= volume;
    arcs[i ^ 1].residual += volume; // 反向边增加容量
    return volume;
    }
    }
    }
    return 0; // 没有找到增广路
    }

    public:
    // 构造函数:初始化图
    Dinic(int v, int e, int s, int t) {
    arcs = new arc[e]; // 分配边数组空间
    vertices = v;
    source = s, sink = t;
    idx = 0;
    head = new int[v];
    dist = new int[v];
    current = new int[v];
    memset(head, 1, sizeof(int) * v); // 初始化邻接表头为 -1
    }

    // 析构函数:释放内存
    ~Dinic() {
    delete [] arcs;
    delete [] head;
    delete [] dist;
    delete [] current;
    }

    // 添加一条有向边及其反向边
    void addArc(int u, int v, int capacity) {
    // 正向边:u -> v
    arcs[idx] = (arc){u, v, capacity, capacity, head[u]};
    head[u] = idx++;

    // 反向边:v -> u,初始容量为 0
    arcs[idx] = (arc){v, u, capacity, 0, head[v]};
    head[v] = idx++;
    }

    // 计算最大流并输出矩阵
    int maxFlow(int r, int c) {
    int flow = 0;

    // Dinic 主循环
    while (bfs()) { // 构建层次网络
    // 当前弧优化:重置当前弧数组
    for (int i = 0; i < vertices; i++)
    current[i] = head[i];

    // 多次 DFS 寻找增广路
    while (int delta = dfs(source, INF))
    flow += delta;
    }

    // 从残余网络中提取矩阵元素
    int matrix[32][32] = {}; // 存储结果矩阵

    // 遍历所有边,找到连接行结点和列结点的边
    for (int i = 0; i < idx; i++) {
    // 如果这条边是从行结点到列结点
    if (1 <= arcs[i].u && arcs[i].u <= r &&
    r + 1 <= arcs[i].v && arcs[i].v <= r + c) {
    // 实际流量 = 原始容量 – 剩余容量
    // 矩阵元素 = 流量 + 1(因为流量表示 A[i][j] – 1)
    matrix[arcs[i].u 1][arcs[i].v 1 r] =
    arcs[i].capacity arcs[i].residual + 1;
    }
    }

    // 输出矩阵
    for (int i = 0; i < r; i++) {
    for (int j = 0; j < c; j++) {
    if (j) cout << ' '; // 元素间用空格分隔
    cout << matrix[i][j];
    }
    cout << '\\n';
    }

    return flow; // 返回最大流值(本题中不需要)
    }
    };

    // 全局数组:存储累积行和与累积列和
    int crs[32]; // Cumulative Row Sums
    int ccs[32]; // Cumulative Column Sums

    int main(int argc, char *argv[]) {
    // 加速输入输出
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);

    int T; // 测试用例数
    cin >> T;

    for (int cs = 0; cs < T; cs++) {
    int R, C; // 矩阵的行数和列数
    cin >> R >> C;

    // 网络流图的顶点:0=源点,1~R=行结点,R+1~R+C=列结点,R+C+1=汇点
    int source = 0, sink = R + C + 1;

    // 创建 Dinic 对象(顶点数50足够,边数10000足够)
    Dinic dinic(50, 10000, source, sink);

    // 读入累积行和
    for (int i = 1; i <= R; i++)
    cin >> crs[i];

    // 读入累积列和
    for (int i = 1; i <= C; i++)
    cin >> ccs[i];

    // 计算行和:r_i = 累积行和(i) – 累积行和(i-1)
    for (int i = R; i >= 1; i)
    crs[i] -= crs[i 1];

    // 计算列和:c_j = 累积列和(j) – 累积列和(j-1)
    for (int i = C; i >= 1; i)
    ccs[i] -= ccs[i 1];

    // 建图:添加主要边
    // 源点到行结点:容量为行和
    for (int i = 1; i <= R; i++)
    dinic.addArc(source, i, crs[i]);

    // 列结点到汇点:容量为列和
    for (int i = 1; i <= C; i++)
    dinic.addArc(R + i, sink, ccs[i]);

    // 添加辅助边以确保流量可行(处理下界约束)
    // 行结点到汇点:容量为 C(每行有 C 个元素,每个至少为 1)
    for (int i = 1; i <= R; i++)
    dinic.addArc(i, sink, C);

    // 源点到列结点:容量为 R(每列有 R 个元素,每个至少为 1)
    for (int i = 1; i <= C; i++)
    dinic.addArc(source, R + i, R);

    // 行结点到列结点:容量为 min(19, 行和-C)
    // 19 是因为元素最大为 20,流量表示 A[i][j]-1,所以最大流量为 19
    // 行和-C 是因为每行有 C 个元素,每个至少为 1,所以可用于分配的流量为 r_i – C
    for (int i = 1; i <= R; i++)
    for (int j = 1; j <= C; j++)
    dinic.addArc(i, R + j, min(19, crs[i] C));

    // 输出格式:测试用例之间空一行(第一个除外)
    if (cs)
    cout << '\\n';

    // 输出标题
    cout << "Matrix " << cs + 1 << '\\n';

    // 计算最大流并输出矩阵
    dinic.maxFlow(R, C);
    }

    return 0;
    }


    代码关键点解释

    1. 网络流建模技巧

    代码中使用了多种技巧来确保流量的正确性:

    • 反向边:每条正向边都有一条对应的反向边,用于实现流量的调整。
    • 层次网络:BFS\\texttt{BFS}BFS 构建距离标号,确保 DFS\\texttt{DFS}DFS 按照最短路径增广。
    • 当前弧优化:避免重复检查已经无法增广的边,提高效率。
    • 辅助边:添加 S−>CjS->C_jS>CjRi−>TR_i->TRi>T 边来处理元素的下界约束(每个元素至少为 111)。

    2. 矩阵元素的提取

    矩阵元素的计算公式为:

    A[i][j] = 原始容量 – 剩余容量 + 1

    这是因为:

    • 原始容量:表示允许的最大流量(即 A[i][j]−1A[i][j]-1A[i][j]1 的最大值)
    • 剩余容量:表示还未使用的流量
    • 实际流量 = 原始容量 – 剩余容量:表示实际使用的流量(即 A[i][j]−1A[i][j]-1A[i][j]1
    • +1:恢复为实际的矩阵元素值

    3. 边界情况处理

    • crs[i]−Ccrs[i] – Ccrs[i]C 可能为负数时,min(19,crs[i]−C)min(19, crs[i] – C)min(19,crs[i]C) 确保容量非负。
    • 题目保证至少有一个解,所以最大流一定能找到可行解。
    • 矩阵大小限制在 20×2020×2020×20 以内,因此数组大小 323232 足够使用。

    总结

    本题的关键在于:

  • 将累积和转化为行和与列和
  • 建立流量网络,将矩阵元素表示为流量+1+1+1
  • 利用 Dinic\\texttt{Dinic}Dinic 算法求解带上下界的流分配问题
  • 输出时注意矩阵元素的取值范围是 111202020
  • 这道题很好地展示了如何将矩阵构造问题转化为网络流问题,是练习 最大流建模 的经典题目。通过添加详细的中文注释,希望能帮助读者更好地理解网络流算法的实现细节和建模技巧。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » UVa 11082 Matrix Decompressing
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!