CF650C Table Compression

CF650C Table Compression

给一个 \(n\times m\) 的非负整数矩阵 \(a\),让你求一个 \(n\times m\) 的非负整数矩阵 \(b\),满足以下条件

  1. \(a_{i,j}<a_{i,k}\),则 \(b_{i,j}<b_{i,k}\)
  2. \(a_{i,j}=a_{i,k}\),则 \(b_{i,j}=b_{i,k}\)
  3. \(a_{i,j}<a_{k,j}\),则 \(b_{i,j}<b_{k,j}\)
  4. \(a_{i,j}=a_{k,j}\),则 \(b_{i,j}=b_{k,j}\)
  5. \(b\) 中的最大值最小

\(n\times m\leq 10^6\)

建图+并查集


先考虑 \(a\) 中没有重复元素的情况

发现,我们只需要对于每行每列,按值域从小到大,相邻两位置连边,然后 \(b\) 每个位置的权值即为到最小数的距离,在 DAG 上遍历一遍即可

但是若 \(a\) 中有重复元素,直接建图就没有正确性了

\(trick\) :对于同一行同一列的重复元素,建立并查集,进行操作时只用对根节点进行操作

时间复杂度 \(O(nm\log nm)\)

代码

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

#define get(x, y) ((x - 1) * m + y)
typedef pair <int, int> pii;
const int maxn = 1e6 + 10;
int n, m, tot, a[maxn], f[maxn], par[maxn];
struct node {
  int x, y;
  bool operator < (const node& o) const {
    return a[get(x, y)] < a[get(o.x, o.y)];
  }
} dat[maxn];
vector <int> g[maxn];

int find(int x) {
  return par[x] == x ? x : par[x] = find(par[x]);
}

void unite(int x, int y) {
  par[find(x)] = find(y);
}

int dfs(int u) {
  if (~f[u]) return f[u]; f[u] = 0;
  for (int v : g[u]) f[u] = max(f[u], dfs(v));
  return ++f[u];
}

int main() {
  scanf("%d %d", &n, &m), tot = n * m;
  for (int i = 1; i <= tot; i++) {
    scanf("%d", a + i), par[i] = i;
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      dat[j] = node{i, j};
    }
    sort(dat + 1, dat + m + 1);
    for (int j = 1; j < m; j++) {
      int u = get(dat[j].x, dat[j].y);
      int v = get(dat[j + 1].x, dat[j + 1].y);
      if (a[u] == a[v]) unite(u, v);
    }
  }
  for (int j = 1; j <= m; j++) {
    for (int i = 1; i <= n; i++) {
      dat[i] = node{i, j};
    }
    sort(dat + 1, dat + n + 1);
    for (int i = 1; i < n; i++) {
      int u = get(dat[i].x, dat[i].y);
      int v = get(dat[i + 1].x, dat[i + 1].y);
      if (a[u] == a[v]) unite(u, v);
    }
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      dat[j] = node{i, j};
    }
    sort(dat + 1, dat + m + 1);
    for (int j = 1; j < m; j++) {
      int u = get(dat[j].x, dat[j].y);
      int v = get(dat[j + 1].x, dat[j + 1].y);
      if ((u = find(u)) != (v = find(v))) g[v].push_back(u);
    }
  }
  for (int j = 1; j <= m; j++) {
    for (int i = 1; i <= n; i++) {
      dat[i] = node{i, j};
    }
    sort(dat + 1, dat + n + 1);
    for (int i = 1; i < n; i++) {
      int u = get(dat[i].x, dat[i].y);
      int v = get(dat[i + 1].x, dat[i + 1].y);
      if ((u = find(u)) != (v = find(v))) g[v].push_back(u);
    }
  }
  memset(f, -1, sizeof f);
  for (int i = 1; i <= tot; i++) {
    if (find(i) == i) dfs(i);
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      printf("%d ", f[find(get(i, j))]);
    }
    putchar(10);
  }
  return 0;
}

一种 \(shortest\) 的做法

对于每个元素,按值域从小到大考虑,通过已访问到的行列最大值更新答案

时间复杂度 \(O(nm\log nm)\)

代码

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

#define get(x, y) ((x - 1) * m + y)
const int maxn = 1e6 + 10;
int n, m, tot, a[maxn], ans[maxn], par[maxn], val[2][maxn];
struct node {
  int x, y;
  bool operator < (const node& o) const {
    return a[get(x, y)] < a[get(o.x, o.y)];
  }
} dat[maxn];

int find(int x) {
  return par[x] == x ? x : par[x] = find(par[x]);
}

int main() {
  scanf("%d %d", &n, &m), tot = n * m;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      int pos = get(i, j);
      scanf("%d", a + pos), dat[pos] = node{i, j}, par[pos] = pos;
    }
  }
  sort(dat + 1, dat + tot + 1);
  for (int i = 1; i <= tot; i++) {
    int tx = dat[i].x, ty = dat[i].y, pos = get(tx, ty);
    int px = find(val[0][tx]), py = find(val[1][ty]), p = find(pos);
    ans[p] = max(ans[px] + (a[p] > a[px]), ans[py] + (a[p] > a[py]));
    if (a[p] == a[px]) par[px] = p;
    if (a[p] == a[py]) par[py] = p;
    val[0][tx] = val[1][ty] = p;
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      printf("%d ", ans[find(get(i, j))]);
    }
    putchar(10);
  }
  return 0;
}

转载于:https://www.cnblogs.com/Juanzhang/p/10424880.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值