OpenJudge Edge Detection 详细解答

目录

Edge Detection

要求:

描述: 

输入: 

输出: 

样例输入:

样例输出: 

提示:

问题分析: 

题目大意:

思路分析:(难)

编程细节:

最终代码:

总结:


​​​​​​​

Edge Detection

要求:

总时间限制: 1000ms

内存限制: 65536kB

描述: 

IONU Satellite Imaging, Inc. records and stores very large images using run length encoding(游程编码). You are to write a program that reads a compressed image, finds the edges in the image, as described below, and outputs another compressed image of the detected edges.
A simple edge detection algorithm sets an output pixel's(像素) value to be the maximum absolute value(绝对值) of the differences between it and all its surrounding pixels in the input image. Consider the input image below:


The upper left pixel in the output image is the maximum of the values |15-15|,|15-100|, and |15-100|, which is 85. The pixel in the 4th row, 2nd column is computed as the maximum of |175-100|, |175-100|, |175-100|, |175-175|, |175-25|, |175-175|,|175-175|, and |175-25|, which is 150.
Images contain 2 to 1,000,000,000 (10^9) pixels. All images are encoded using run length encoding (RLE). This is a sequence of pairs, containing pixel value (0-255) and run length (1-10^9). Input images have at most 1,000 of these pairs. Successive(连续的) pairs have different pixel values. All lines in an image contain the same number of pixels.

输入: 

Input consists of information for one or more images. Each image starts with the width, in pixels, of each image line. This is followed by the RLE pairs, one pair per line. A line with 0 0 indicates the end of the data for that image. An image width of 0 indicates there are no more images to process. The first image in the example input encodes the 5x7 input image above. 

输出: 

Output is a series of edge-detected images, in the same format as the input images, except that there may be more than 1,000 RLE pairs.

样例输入:

7
15 4
100 15
25 2
175 2
25 5
175 2
25 5
0 0
10
35 500000000
200 500000000
0 0
3
255 1
10 1
255 2
10 1
255 2
10 1
255 1
0 0
0

样例输出: 

7
85 5
0 2
85 5
75 10
150 2
75 3
0 2
150 2
0 4
0 0
10
0 499999990
165 20
0 499999990
0 0
3
245 9
0 0
0

提示:

A brute(暴力蛮干的) force solution that attempts to compute an output value for every individual pixel will likely fail due to space or time constraints.

问题分析: 

题目大意:

 这个题目可以说是非常困难了. IONU Satellite Imaging Inc.记录了一些超大图片,最原本图片的记录方式是记录每个像素点的值(形成一个矩阵),然而IONU采用游程编码来取代矩阵以减小存储所需的空间.

所谓游程编码RLE(Run Length Encoding)就是指将连续的值(AAAAA)记作值与长度的RLE对(A 5或5 A). IONU记录了原图的所有RLE对,但是IONU希望对图像做出一定的分析,寻找边界,而此题中的寻找方式是计算每个点在矩阵中与周围相邻所有点(可能是8个,也可能不是)的差的最大绝对值. 我们所需要做的就是输出转换之后的图像(依旧使用RLE对的方式进行输出).

题目给我们的输入除了所有的RLE对以外,还会告诉我们图像的宽度(列数), 在输出时也要先输出宽度,并且所有的图像都是矩形.一张图的输入以0 0RLE对结束,所有输入的结束以0宽度结束.

通过提示我们知道,暴力地通过定义,先转换为矩阵,再计算每个点的转换值,再还原成RLE对,这样做一定是不会通过的.所以我们需要找到一个更好的算法来解决问题.

思路分析:(难)

其实当我们注意到样例输入中的第二个图的时候就可以知道,转换成矩阵的方式是不可行的,因为RLE对的第二项length是可以达到10^9之巨的,转换成矩阵计算太浪费空间. 所以自然的想法是不可取的,我们只能在RLE对上进行操作.

那么通过RLE对,我们可以获取什么信息呢?最直接的有两个:1)像素值;2)该像素值的连续长度. 但单单只有这两种是不够的,我们需要输出的结果是RLE对,所以我们需要知道的最重要的元素就是转换后图像的每一段相同像素值的起点与终点. 而实际上只知道转换后每一段像素值的起点即可,因为一段的终点一定是下一段的起点之前的一个像素.

所以我们的目标就变成了,寻找转换后每一个相同像素段的起点{\color{Red}\textbf{x} }. 所以我们考虑这样的起点的特征,在这里我们分两类进行讨论:(以下讨论中xx'所代表像素值不同) 

(一)如果这个点 x 与之前一个点 x'=x-1 之间没有换行的话,就会出现下面的几种情况:

A{\color{Green}\alpha }{\color{Green}\beta }{\color{Green} \gamma }             D
Bx'xE
CF

 由于 xx'所代表转换后像素值不同,所以有序对\left \langle A,B,C \right \rangle\langle D,E,F\rangle一定不同. 不妨设AD不相同,那么考虑上图的第一行,原图的对应D 像素值的RLE对起点一定位于上方的绿色位置之一。由于第一第二第三行均可能出现不同,所以以 x 为中心的3x3九宫格中都可能会有原图的RLE对起点. 那么当我们转换一个参考系后,以原图的RLE对起点为参考点,那么转换后的RLE对的起点一定位于某个原图起点的3x3九宫格中.

我们上面讨论的情况是保证左侧有\left \langle A,B,C \right \rangle且右侧有\langle D,E,F\rangle. 在这里,我们需要考虑一种特殊情况:如果没有\left \langle A,B,C \right \rangle\langle D,E,F\rangle,那么上述推理是否依然成立?

APQ
Bx'x
CMN

  由于 xx'所代表转换后像素值不同, 所以ABC中一定至少有一个元素不含在\{P,Q,M,N,x',x \}中,不妨设为A,则 P 为原图RLE对起点. 同理,x'M均可能为原图的RLE对起点,这样的结果依然符合我们上述推出的结果:以原图的RLE对起点为参考点,那么转换后的RLE对的起点一定位于某个原图起点的3x3九宫格中.

(二)如果这个点 x 与之前一个点 x'=x-1 之间换行的话,就会出现下面的几种情况:

{\color{Cyan}\alpha }{\color{Cyan}\alpha }
{\color{Teal} \alpha}{\color{Teal} \alpha}{\color{Cyan}\alpha }x'
x{\color{Teal} \alpha}{\color{Cyan}\alpha }{\color{Cyan}\alpha }
{\color{Teal} \alpha}{\color{Teal} \alpha}

在这种情况中我们注意到虽然xx'所代表的转换后像素值不同,但由于二者所相邻的5个方格基本没有任何关系,所以根据这种情况来推出相关结论是比较困难的. 所以我们不妨转换思路,看看上述的九宫格结论在此处是否仍然适用?(这里的九宫格是广义的,代表的是{x-Width-1, x-Width, x-Width+1, x-1, x, x+1, x+Width-1, x+Width, x+Width+1}这9个点,这些点在矩阵中可能因为换行并不呈现方格状

G
DAx'(H)
x(E)BI
FC

 我们设出如图的九个点,不妨反设上述九宫格结论不成立,那么所设的这九个点一定不能是原图中的RLE对起点,否则依靠上述的九宫格结论可以找到这个转换后的RLE对起点x. 所以根据红字,我们就得到了这样的图:

AA
AABB(x')
B(x)BCC
CC

 很明显,此时 xx'所代表的转换后像素值是相同的,与我们假定的条件相矛盾,所以九宫格结论在这种情况下是成立的.

但是问题还没有结束,还有两种特殊情况:

1)当 x' 位于第一行时,也就是取上图的下三行的情况,根据上述反证法的过程,易得下图。此时 xx'所代表的转换后像素值是相同的,与我们假定的条件相矛盾,所以九宫格结论在这种情况下是成立的.

AABB​(x'​)
B​(x​)BCC
CC

 2)当 x 位于最后一行时,也就是取“上上图”的上三行的情况,根据上述反证法的过程,易得下图。注意⚠️此时出现了 x' 不一定等于 x 的情况,代表九宫格结论在这种情况下是不成立的,所以对于这种情况我们需要单独讨论,也就是单独考虑图中左下角点的转换值.

AA
AABB​(x'​)
B​(x​)BCC

此时我们的思路已经出来了,考虑每个原图中RLE对的起点的广义九宫格区域9个点的转换值,以及图中左下角点的转换值,就可以包含所有转换后RLE对的起点,从而求出转换后的所有RLE对,输出答案.

编程细节:

根据题目输入中的信息,我们知道原图中的RLE对不会超过1000个,所以我们可以考虑在构建一个结构体后,设一个长度1000的结构体数组,或者也可以直接用vector.(如果不会结构体和构造函数,此题还是算了吧)

struct RLE{
    int Value;//像素值
    int Length;//长度
    int Point;//起点位置,从1开始
    RLE(int a,int b,int c=1):Value(a),Length(b),Point(c){};//构造函数
};vector<RLE> Image;

在输入过程中,由于我们需要知道最终一共有多少行,所以我们需要统计一下总共的像素格数,由于题中给出像素格总数不超过10^9,所以用int就足够了.

scanf("%d",&Width);//宽度
    while(Width!=0){
        printf("%d\n",Width);
      int TotalPixel=0;//总像素格数
      int Value,Length;//只是为了输入
      scanf("%d %d", &Value,&Length);
      while(Value!=0||Length!=0) {
          Image.emplace_back(Value, Length, TotalPixel + 1);//emplace_back可以直接使用构造函数,此处用push_back也可以,就是需要先构造完,再push_back
          TotalPixel += Length;
          scanf("%d %d",&Value,&Length);
      }
      Row=TotalPixel/Width;//长度(行)

由于我们需要采集一些点(九宫格+左下角)的转换后的值,并且最终需要按顺序进行排序,从而输出RLE对,这里用map是最方便的.

map<int,int> Edge;//第一位对应顺序,从小到大,第二位对应转换后像素值

具体的将九宫格以及左下角的点的转换值放入map中的过程我就不再这里展示了,比较繁琐,需要很多判断,如果需要,可以在“最终代码”处进行查阅(会有注释).

最后输出的时候:

 int p1=Edge.begin()->first;int p2=Edge.begin()->second;
for(auto it=Edge.begin();it!=Edge.end();it++){
          if(it->second!=p2){//如果出现了新的转换值,代表之前的转换值RLE对已经结束,可以输出了
              printf("%d %d\n",p2,it->first-p1);
              p1=it->first;
              p2=it->second;//更新一下
          }
      }
      printf("%d %d\n0 0\n",p2,TotalPixel-p1+1);//结尾,前两个int是最后一段RLE对,后两个0是输出要求

最终代码:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
struct RLE{
    int Value;
    int Length;
    int Point;
    RLE(int a,int b,int c=1):Value(a),Length(b),Point(c){};
};
int Width;int Row;vector<RLE> Image;map<int,int> Edge;
inline int EdgeDetect(int x){//计算目标点x转换后的值
    int ans=0;
    vector<int> Detect;//周围点
    if(x%Width!=1&&(x-1)/7!=0&&x-Width-1>0) Detect.push_back(x-Width-1);
    if((x-1)/7!=0&&x-Width>0) Detect.push_back(x-Width);
    if(x%Width!=0&&(x-1)/7!=0&&x-Width+1>0) Detect.push_back(x-Width+1);
    if(x%Width!=1&&x-1>0) Detect.push_back(x-1);
    if(x%Width!=0&&x+1<=Row*Width) Detect.push_back(x+1);
    if(x%Width!=1&&(x-1)/Width!=Row-1&&x+Width-1<=Row*Width) Detect.push_back(x+Width-1);
    if((x-1)/Width!=Row-1&&x+Width<=Row*Width) Detect.push_back(x+Width);
    if(x%Width!=0&&(x-1)/Width!=Row-1&&x+Width+1<=Row*Width) Detect.push_back(x+Width+1);
//目标点x周围点都是否在矩阵范围之内
    int value;
    for(int i=0;i<Image.size();i++){
        if(Image[i].Point+Image[i].Length>x){
            value=Image[i].Value;
            break;
        }
    }//找出目标点x的原像素值
    int k=0;
    for(int i=0;i<Image.size();i++){
        while(Image[i].Point+Image[i].Length>Detect[k]){
            ans=max(ans,abs(Image[i].Value-value));//和周围点进行绝对值计算
            k++;
            if(k==Detect.size()) break;
        }
        if(k==Detect.size()) break;
    }
    return ans;//返回转换后的值
}
int main(){
    scanf("%d",&Width);
    while(Width!=0){
        printf("%d\n",Width);
      int TotalPixel=0;
      int Value,Length;
      scanf("%d %d", &Value,&Length);
      while(Value!=0||Length!=0) {
          Image.emplace_back(Value, Length, TotalPixel + 1);
          TotalPixel += Length;
          scanf("%d %d",&Value,&Length);
      }
      Row=TotalPixel/Width;
      for(int i=0;i<Image.size();i++){
          Edge.insert(make_pair(Image[i].Point,EdgeDetect(Image[i].Point)));//map的输入方式是insert和make_pair
          if(Image[i].Point-1>0) {
              if (Image[i].Point - Width + 1 > 0) {
                  if (Image[i].Point - Width > 0) {
                      if (Image[i].Point - Width - 1 > 0) {
                          Edge.insert(make_pair(Image[i].Point - Width - 1,
                                                EdgeDetect(Image[i].Point - Width - 1)));
                      }
                      Edge.insert(make_pair(Image[i].Point - Width,
                                            EdgeDetect(Image[i].Point - Width)));
                  }
                  Edge.insert(make_pair(Image[i].Point - Width + 1,
                                        EdgeDetect(Image[i].Point - Width + 1)));
              }
              Edge.insert(make_pair(Image[i].Point - 1, EdgeDetect(Image[i].Point - 1)));
          }
          if(Image[i].Point+1<=TotalPixel) {
              if (Image[i].Point + Width - 1 <= TotalPixel) {
                  if (Image[i].Point + Width <= TotalPixel) {
                      if (Image[i].Point + Width + 1 <= TotalPixel) {
                          Edge.insert(make_pair(Image[i].Point + Width + 1,
                                                EdgeDetect(Image[i].Point + Width + 1)));
                      }
                      Edge.insert(make_pair(Image[i].Point + Width,
                                            EdgeDetect(Image[i].Point + Width)));
                  }
                  Edge.insert(make_pair(Image[i].Point + Width - 1,
                                        EdgeDetect(Image[i].Point + Width - 1)));
              }
              Edge.insert(make_pair(Image[i].Point + 1, EdgeDetect(Image[i].Point + 1)));
          }
//两个大if和开头的insert代表了输入所有原图RLE对的起点的九宫格
              Edge.insert(make_pair(TotalPixel-Width+1,EdgeDetect(TotalPixel-Width+1)));//原图的左下角点
      }
      int p1=Edge.begin()->first;int p2=Edge.begin()->second;
      for(auto it=Edge.begin();it!=Edge.end();it++){
          if(it->second!=p2){
              printf("%d %d\n",p2,it->first-p1);
              p1=it->first;
              p2=it->second;
          }
      }
      printf("%d %d\n0 0\n",p2,TotalPixel-p1+1);
      scanf("%d",&Width);
      Image.clear();Edge.clear();//由于设的是全局变量,所以每一张图结束后需要clear
    }
    printf("0\n");
}

总结:

此题的难度系数非常高,从RLE对的处理,到中间的算法思路,再到最后的代码实现,可以说是既繁又难。但是这样的题对于做题者的收获也很大. 中间的算法分析尤其需要细细品味,搞清楚其中的思想方法是最重要的,对于观者,最好还是亲自编一下,才能体会其中的细节。如果这篇文章对您有所帮助的话,请不要忘了点赞关注哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值