CSP-202212-3 JPEG解码

V1:循环方式

  • 按照题意一步步实现,命名为step1至6方便对照。
  • 用方向变量记录填充路线,碰到边界后转向。
  • 计算量最大的是逆变换的4层循环。中途不变的表达式提取到外层,但影响不大。
  • print设计为函数模板,输出整型、浮点型矩阵便于调试。
  • 评测结果:正确 100 15ms 4.312MB
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
using namespace std;

constexpr int K = 8;
int Q[K][K];
int M[K][K];
double P[K][K];

template <typename T>
void print(const T a[K][K]) {
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      cout << a[i][j] << " \n"[j == 7];
    }
  }
}

int clamp(int x, int lo, int hi) { return (x < lo ? lo : (x > hi ? hi : x)); }

void step1() { // 读入量化矩阵
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      cin >> Q[i][j];
    }
  }
}

void step3(int n) { // 读入扫描数据,折返填充
  for(int r = 0, c = 0, dir = 'U'; n > 0 && cin >> M[r][c]; --n) {
    if(dir == 'U') { // 斜向左上
      --r;
      if(r < 0) {
        ++r;
        ++c;
        dir = 'D';
      } else {
        ++c;
        if(c >= K) {
          r += 2;
          --c;
          dir = 'D';
        } else {
        }
      }
    } else {
      assert(dir == 'D');
      ++r;
      if(r >= K) {
        --r;
        ++c;
        dir = 'U';
      } else {
        --c;
        if(c < 0) {
          ++c;
          dir = 'U';
        }
      }
    }
  }
}

void step4() {
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      M[i][j] *= Q[i][j];
    }
  }
}

void step5() {
  const double r2 = sqrt(0.5);
  const double p8 = acos(-1) / K;
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      for(int u = 0; u < K; ++u) {
        const double a = (u == 0 ? r2 : 1);
        const double c1 = cos(p8 * (i + 0.5) * u);
        for(int v = 0; v < K; ++v) {
          const double b = (v == 0 ? r2 : 1);
          const double c2 = cos(p8 * (j + 0.5) * v);
          P[i][j] += a * b * M[u][v] * c1 * c2;
        }
      }
      P[i][j] /= 4;
    }
  }
}

void step6() {
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      M[i][j] = clamp(P[i][j] + 128.5, 0, 255);
    }
  }
}

int main() {
  step1();
  // step2: 全局数据自动初始化。
  int n, T; // 扫描数据的个数,任务(步骤)
  cin >> n >> T;
  step3(n);
  if(T >= 1) {
    step4();
  }
  if(T >= 2) {
    step5();
    step6();
  }
  print(M);
  return 0;
}

V2:函数式写法

V1版有多处类似的双层8x8循环,用函数式风格简化:

  • 把双层循环抽象成映射。
  • 将内层动作抽象为多个函数。用lambda表达式也可以。
  • 用索引数组实现折返填充。

整体约缩减一半代码量,但不如V1版直观。

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

constexpr int K = 8;
const int IDX[] = {0,  1,  8,  16, 9,  2,  3,  10, 17, 24, 32, 25, 18,
                   11, 4,  5,  12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
                   13, 6,  7,  14, 21, 28, 35, 42, 49, 56, 57, 50, 43,
                   36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
                   38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
int n, T, Q[K][K], M[K][K]; // 扫描数据的个数,任务(步骤)
double P[K][K];
const double r2 = sqrt(0.5);
const double p8 = acos(-1) / 8; // 八分之Pi

template<typename F>
void loop(F f) { // 将函数f映射到8x8矩阵网格上
  for(int i = 0; i < K; ++i) {
    for(int j = 0; j < K; ++j) {
      f(i, j);
    }
  }
}

int clamp(int x, int l, int h) { return x < l ? l : (x > h ? h : x); }
double alpha(int u) { return u == 0 ? r2 : 1; }
double cs(int i, int u) { return cos(p8 * (i + 0.5) * u); }
void load(int i, int j) { cin >> Q[i][j]; }
void scan(int i, int j) { if(n-- > 0) { cin >> M[0][IDX[i * 8 + j]]; } }
void mult(int i, int j) { M[i][j] *= Q[i][j]; }
void bias(int i, int j) { M[i][j] = clamp(P[i][j] + 128.5, 0, 255); }
void dump(int i, int j) { cout << M[i][j] << " \n"[j == 7]; }

void dct(int i, int j) {
  double& e = P[i][j];
  auto converter = [&e, i, j](int u, int v) {
    e += alpha(u) * alpha(v) * M[u][v] * cs(i, u) * cs(j, v);
  };
  loop(converter);
  e /= 4;
}

int main() {
  loop(load); // step 1。全局数据自动初始化,step2略。
  cin >> n >> T;
  loop(scan);
  if(T >= 1) {
    loop(mult); // step 4
  }
  if(T >= 2) {
    loop(dct); // step 5
    loop(bias); // step 6
  }
  loop(dump);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值