题解 AT678

· · 题解

经过了一周的努力后,终于把这题过了......

题意描述:

给你一张有数字和字符的图片,计算其表达式的值。

分析:

对于这道题来说,大体可以分为2个步骤:

其中表达式求值的部分可参考P1175(表达式的转换),将中缀表达式转为后缀表达式求值即可。

但最重要,也是最难以解决的部分,就是识别表达式的部分,应该怎么做呢?

首先,为了减少噪点的干扰,需要对图片进行降噪处理,这里,我采用了一种叫众数滤波器的降噪方法,这里展示的是对52号点的处理结果:

可以看到,效果相当显著。具体的代码实现也比较简单:

//对图片进行降噪处理
Image reducenoice(const Image& img)
{
    Image tmp(img);  //对img进行复制
    auto valid = [&](int x, int y) {return x >= 0 and x < img.sx and y >= 0 and y < img.sy; };  //判定访问是否越界
    int dx[] = { 0, 1, 1, 0, -1, -1, -1, 0, 1 };
    int dy[] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };   //创建遍历列表
    for (int x = 0; x < img.sx; x++) {
        for (int y = 0; y < img.sy; y++) {  //对每个x, y进行循环
            int cnt1 = 0, cnt2 = 0;  //cnt1为周围1圈黑点数量,cnt2为周围1圈点的数量
            for (int k = 0; k < 9; k++) {  //遍历周围的点
                int nx = x + dx[k], ny = y + dy[k];
                if (valid(nx, ny))
                    cnt2++, cnt1 += img[nx][ny];
            }
            tmp[x][y] = (cnt1 * 2 > cnt2);  //判定周围黑点数量是否比白点多,若是则将此处涂黑
        }
    }
    return tmp;
}

(其中Image是我为了方便操作图片而创建的类)

对整张图片进行降噪后,我们就可以准确地将各个字符从图片中按顺序分离出来了。因为题目保证了:

  • 对于横向的位置,对于任意相邻的字符,它们之间会有 10 个像素的空白。

所以我们可以将图片逐列从上往下扫,来按顺序得到不同的字符。

这一部分的代码实现:

//将大于minsize大小的联通块分离出来
vector<Image> split(Image img, int minsize = 15)
{
    vector<Image> res;
    queue<pair<int, int>> q;  //存储连通块中的黑点
    int minx = INT_MAX, maxx = INT_MIN, miny = INT_MAX, maxy = INT_MIN;
    auto init = [&]() {minx = INT_MAX, maxx = INT_MIN, miny = INT_MAX, maxy = INT_MIN; };  //初始化变量
    auto valid = [&](int x, int y) { return x >= 0 and x < img.sx and y >= 0 and y < img.sy; };  //判定访问是否越界
    auto bfs = [&](int x, int y) {  //将当前连通块中所有黑点全部加入到队列q中
        queue<pair<int, int>> q1;
        int dx[] = { 0, 1, 0, -1 }, dy[] = { 1, 0, -1, 0 };
        q1.push({ x, y }); q.push(q1.back());
        chkmax(maxx, x), chkmax(maxy, y), chkmin(minx, x), chkmin(miny, y);
        img[x][y] = 0;
        while (!q1.empty()) {
            auto h = q1.front();
            int x = h.first, y = h.second;
            q1.pop();
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (valid(nx, ny) and img[nx][ny]) {
                    q1.push({ nx, ny }); q.push(q1.back());
                    img[nx][ny] = 0;
                    chkmax(maxx, nx), chkmax(maxy, ny), chkmin(minx, nx), chkmin(miny, ny);
                }
            }
        }
    };
    for (int j = 0; j < img.sy; j++) {
        for (int i = 0; i < img.sx; i++) {  //遍历整张图,注意变量枚举顺序
            if (img[i][j]) {  //如果此处存在黑点
                init();
                bfs(i, j);  //得到与它相连的连通块
                if (q.size() >= minsize) {  //这里的判断是防止有些噪点未被处理干净,被当为单个字符分离出去
                    Image tmpimg(maxx - minx + 1, maxy - miny + 1, img.conv1, img.conv2);  //依据大小构建子图
                    while (!q.empty()) {
                        auto p = q.front(); q.pop();
                        int x = p.first, y = p.second;
                        tmpimg[x - minx][y - miny] = 1;  //将队列里的黑点放到子图的相应位置
                    }
                    res.push_back(tmpimg);  //将子图放到列表里面
                }
                while (!q.empty()) q.pop();
            }
        }
    }
    return res;
}

剩下来的,就是单个字符的识别过程了。到了这里,可能会束手无策,但是题面又一次给了我们重要的提示:

输入:

t \vdots

其中,t 为非负整数,表示此为第 t 组数据;

所有以下数据按以下步骤生成:

\cdots

数据范围:

  • 对于 0 \le t < 30 的数据:
  • \vdots
  • 对于 30 \le t < 90 的数据:
  • \vdots
  • 对于 90 \le t \le 140 的数据:
  • \vdots

这说明什么?对于出现的所有字符,它都是通过确定的变换方式,通过不同的参数变换而来的。根据题意,可以写出变换部分的代码:

Image balance(const Image& img)
{
    Image res(65, 38);
    int sumx = 0, sumy = 0, sumc = 0;
    for (int i = 0; i < img.sx; i++)
    {
        for (int j = 0; j < img.sy; j++)
        {
            if (img[i][j])
            {
                sumx += i, sumy += j, sumc++;
            }
        }
    }
    int cx = (2 * sumx + sumc) / (2 * sumc), cy = (2 * sumy + sumc) / (2 * sumc);  //确定中心位置
    int px = 32 - cx, py = 19 - cy;  //确定偏移量
    res = cover(res, img, px, py);  //进行平移
    return res;
}

auto trans = [&](const Image& img, double M, double Mx, double My, double R, double Sx, double Sy)
{
    auto valid = [=](int x, int y) { return x >= 0 and x < 65 and y >= 0 and y < 38; };  //判断是否越界
    Image imgans(65, 38);
    for (int i = 0; i < 65; i++)
    {
        for (int j = 0; j < 38; j++)
        {
            if (img[i][j])
            {
                double y = i + 0.5 - 32.5, x = j + 0.5 - 19;  //先对像素点进行平移,方便后面的旋转,扭曲操作
                x *= M * Mx, y *= M * My;  //进行横向/纵向伸缩变换
                double nx = x * cos(torad(R)) - y * sin(torad(R)), ny = x * sin(torad(R)) + y * cos(torad(R));  //进行旋转变换
                x = nx + Sy * ny, y = ny + Sx * nx;  //进行扭曲变换
                int X = int(round(y + 32.5 - 0.5)), Y = int(round(x + 19 - 0.5));  //最终的X, Y坐标
                if (valid(X, Y))
                {
                    imgans[X][Y] = 1;
                }
            }
        }
    }
    return balance(reducenoice(imgans));  //对变换后的图像降噪后再摆正位置
};

有了变换函数了后,我们就可以在题目给定的参数范围内,随机生成多张图像,来应对可能出现的多种情况。这里是初始化函数的实现:

void init(int t)
{
    for (int i = 0; i < 16; i++)
    {
        Signset[i] = cover(Image(65, 38), Signset[i], Signpos[i][0], Signpos[i][1]);  //这里我为了减少代码长度,只截取了各个字符的黑色区域,保存了它们的左上角坐标,这一步做的就是还原他们原来所在的位置
    }
    double Mmin = 0.9, Mmax = 1, Rmin, Rmax, Smin, Smax;
    if (0 <= t and t < 30)
    {
        Rmin = -2, Rmax = 2, Smin = Smax = 0;
    }
    else if (30 <= t and t < 90)
    {
        Rmin = -10, Rmax = 10, Smin = -0.1, Smax = 0.1;
    }
    else
    {
        Rmin = -15, Rmax = 15, Smin = -0.1, Smax = 0.1;
    }
    uniform_real_distribution M(Mmin, Mmax), R(Rmin, Rmax), S(Smin, Smax);  //初始化随机数生成器
    default_random_engine gen(time(NULL));  //初始化随机数生成器
    auto trans = [&](const Image& img, double M, double Mx, double My, double R, double Sx, double Sy)
    {
        auto valid = [=](int x, int y) { return x >= 0 and x < 65 and y >= 0 and y < 38; };  //判断是否越界
        Image imgans(65, 38);
        for (int i = 0; i < 65; i++)
        {
            for (int j = 0; j < 38; j++)
            {
                if (img[i][j])
                {
                    double y = i + 0.5 - 32.5, x = j + 0.5 - 19;  //先对像素点进行平移,方便后面的旋转,扭曲操作
                    x *= M * Mx, y *= M * My;  //进行横向/纵向伸缩变换
                    double nx = x * cos(torad(R)) - y * sin(torad(R)), ny = x * sin(torad(R)) + y * cos(torad(R));  //进行旋转变换
                    x = nx + Sy * ny, y = ny + Sx * nx;  //进行扭曲变换
                    int X = int(round(y + 32.5 - 0.5)), Y = int(round(x + 19 - 0.5));  //最终的X, Y坐标
                    if (valid(X, Y))
                    {
                        imgans[X][Y] = 1;
                    }
                }
            }
        }
        return balance(reducenoice(imgans));  //对变换后的图像降噪后再摆正位置
    };
    for (int i = 0; i < 16; i++)  //枚举每个字符
    {
        for (int j = 0; j < maxitertime; j++)  //在参数范围内随机生成至多maxitertime张图像,这里的数字为75
        {
            double _M = M(gen), _Mx = M(gen), _My = M(gen), _R = R(gen), _Sx = S(gen), _Sy = S(gen);  //获取随机数
            randomset[i][j] = trans(Signset[i], _M, _Mx, _My, _R, _Sx, _Sy);  //生成随机图像
        }
    }
}

对单字符的匹配也能顺便写出来了:

const char* convtable = "0123456789()+-*/";  //字符转换表

double operator==(const Image& img1, const Image& img2)
{
    int bx = img2.sx, by = img2.sy;
    double cnt = 0;
    for (int i = 0; i < img1.sx; i++)
    {
        for (int j = 0; j < img1.sy; j++)
        {
            cnt += (img2[i][j] == img1[i][j]) ? 1 : 0;
        }
    }
    return (cnt / double(img1.sx * img1.sy));
}

char recognize(const Image& img)
{
    double rate = 0;
    int idx = -1;  
    Image imgtmp = balance(img);  //调整img的位置
#ifdef debug
    cout << "Balancing the input :\n" << imgtmp << endl;
#endif // debug

    for (int i = 0; i < maxitertime; i++)
    {
        for (int j = 0; j < 16; j++)
        {
            double r = (imgtmp == randomset[j][i]);  //比较相似程度
            if (r > rate)  //如果匹配度最大
            {
                rate = r;  //记录匹配度
                idx = j;  //记录编号
            }
        }
    }
    return convtable[idx];  //返回对应的字符
}

最后,位于主函数的的计算部分也能写出来了:

int main()
{
    string tmp1, tmp2;
    cin >> t >> n >> m;
    init(t);  //根据数据编号初始化
    while (cin >> tmp2)
    {
        tmp1 += tmp2 + ' ';
    }
    test = Image(tmp1);  //从字符串构造图像
    test = reducenoice(test);  //对图像进行降噪
    //cout << test << endl << endl;
    test2 = split(test);  //分离单个字符
    tmp2.clear();
    for (const auto& i : test2)
    {
        //cout << i << endl << endl;
        tmp2 += recognize(i);  //分别对列表中的字符逐一识别,并加到字符串中
    }
    cout << calc(tmp2) << endl;  //最后调用计算函数,完成计算(一定要加换行符啊!)
}

这道题就这么结束了

code: (其中有很多我写代码时用来调试的无用代码...)

#include <bits/stdc++.h>
#pragma warning(disable : 4996)
#define runmode 3
//#define debug 1
using namespace std;
const double pi = 3.141592653589793;
const int maxitertime = 75;
#define Range(x) (x).begin(), (x).end()
template <typename T>
void chkmax(T& x, T y) { x = max(x, y); }
template <typename T>
void chkmin(T& x, T y) { x = min(x, y); }
double torad(double x) { return x / 180.0 * pi; }
const function<int(char)> conv1 = [](const char c) {return c == '.' ? 0 : 1; };
const function<char(int)> conv2 = [](const int c) {return c ? '#' : '.'; };
const char* convtable = "0123456789()+-*/";  //字符转换表
//ofstream out("data.out");

struct Image
{
    vector<vector<int>> raw;
    function<int(char)> conv1 = [](const char c) {return int(c); };
    function<char(int)> conv2 = [](const int c) {return char(c); };
    Image() = default;
    int sx, sy;
    Image(int sx, int sy, function<int(char)> c1 = ::conv1, function<char(int)> c2 = ::conv2) :sx(sx), sy(sy), conv1(c1), conv2(c2) {
        raw.resize(sx);
        fill(Range(raw), vector<int>(sy));
    }
    auto& operator[](int x) { return raw[x]; }
    const auto& operator[](int x) const { return raw[x]; }
    //从字符串构造图片
    Image(const string& a, function<int(char)> c1 = ::conv1, function<char(int)> c2 = ::conv2) : conv1(c1), conv2(c2) {
        sx = sy = 0;
        stringstream s1;
        string s2;
        s1 << a;
        while (s1 >> s2) {
            vector<int> s3;
            for (const auto i : s2) {
                s3.push_back(conv1(i));
            }
            raw.push_back(s3);
            sx++;
            chkmax(sy, int(s3.size()));
        }
    }
    Image(const char* a, function<int(char)> c1 = ::conv1, function<char(int)> c2 = ::conv2) : conv1(c1), conv2(c2) {
        sx = sy = 0;
        stringstream s1;
        string s2;
        s1 << a;
        while (s1 >> s2) {
            vector<int> s3;
            for (const auto i : s2) {
                s3.push_back(conv1(i));
            }
            raw.push_back(s3);
            sx++;
            chkmax(sy, int(s3.size()));
        }
    }
    friend int colorcount(const Image& img)
    {
        int cnt = 0;
        for (const auto& i : img.raw)
        {
            for (const auto& j : i)
            {
                cnt += j;
            }
        }
        return cnt;
    }
    Image subImage(int sx, int sy, int ex, int ey) const
    {
        Image res(ex - sx + 1, ey - sy + 1);
        for (int i = sx; i <= ex; i++)
        {
            for (int j = sy; j <= ey; j++)
            {
                res[i - sx][j - sy] = raw[i][j];
            }
        }
        return res;
    }
    tuple<int, int, int, int> getminmaxrange() const
    {
        int minx = INT_MAX, maxx = 0, miny = INT_MAX, maxy = 0;
        for (int i = 0; i < sx; i++)
        {
            for (int j = 0; j < sy; j++)
            {
                if (raw[i][j])
                {
                    chkmax(maxx, i);
                    chkmin(minx, i);
                    chkmax(maxy, j);
                    chkmin(miny, j);
                }
            }
        }
        return { minx, miny, maxx, maxy };
    }
    bitset<65 * 38> tobitset() const
    {
        bitset<65 * 38> res;
        assert(sx == 65 and sy == 38);
        for (int i = 0; i < 65; i++)
        {
            for (int j = 0; j < 38; j++)
            {
                res[i * 38 + j] = raw[i][j];
            }
        }
        return res;
    }
    void resize(int nx, int ny){
        sx = nx, sy = ny;
        raw.resize(ny);
        for_each(Range(raw), [=](auto& i) { i.resize(nx); });
    }
    friend ostream& operator<<(ostream& out, const Image& img) {
        for (const auto& i : img.raw) {
            for (const auto j : i)
                cout << img.conv2(j);
            cout << endl;
        }
        return out;
    }
};

#pragma region data
vector<Image> Signset{  //各个字符对应的图像
R"(
...........########...........
.........############.........
.......################.......
......##################......
.....####################.....
....######################....
...########################...
...##########....##########...
..#########........#########..
..########..........########..
.#########..........#########.
.########............########.
.########............########.
.########............########.
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
########..............########
.########............########.
.########............########.
.########............########.
.#########..........#########.
..########..........########..
..#########........#########..
...##########....##########...
...########################...
....######################....
.....####################.....
......##################......
.......################.......
.........############.........
...........########...........
)",
R"(
.............####..........
...........#######.........
........##########.........
.....#############.........
..################.........
.#################.........
.#################.........
.#################.........
.#################.........
..######..########.........
..###.....########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
..........########.........
.##########################
###########################
###########################
###########################
###########################
.##########################
)",
R"(
.........#########..........
......##############........
...###################......
..#####################.....
.#######################....
.#######################....
.########################...
.#########......#########...
.#######.........#########..
.#######..........########..
.#######..........########..
.#######..........########..
.#######..........########..
..######..........########..
.................#########..
.................########...
................#########...
...............##########...
..............##########....
.............###########....
............###########.....
...........###########......
..........###########.......
.........###########........
........###########.........
.......###########..........
......###########.....#####.
.....###########.....#######
....###########......#######
...###########.......#######
..###########........#######
.###########.........#######
############################
############################
############################
############################
############################
.###########################
)",
R"(
.........##########.........
.....################.......
...####################.....
..######################....
..#######################...
..########################..
..########################..
..########.......##########.
..#######.........#########.
..#######..........########.
..#######..........########.
...######..........########.
...................########.
...................########.
..................########..
.................#########..
..........###############...
.........###############....
.........##############.....
.........###############....
.........################...
..........################..
.................##########.
...................########.
...................#########
....................########
....................########
....................########
....................########
...................#########
..###.............##########
.########........##########.
.##########################.
.#########################..
##########################..
.########################...
.######################.....
....#################.......
.......###########..........
)",
R"(
.................#####........
................#######.......
...............########.......
..............#########.......
.............##########.......
............###########.......
............###########.......
...........############.......
..........#############.......
.........##############.......
........###############.......
.......################.......
.......########.#######.......
......########..#######.......
.....########...#######.......
....########....#######.......
...#########....#######.......
...########.....#######.......
..########......#######.......
.########.......#######.......
##############################
##############################
##############################
##############################
##############################
##############################
...............########.......
...............########.......
...............########.......
...............########.......
...............########.......
...............########.......
.........####################.
........#####################.
........#####################.
........#####################.
........#####################.
.........####################.
)",
R"(
...######################...
...#######################..
...#######################..
...#######################..
...#######################..
...######################...
...#######..................
...#######..................
...#######..................
...#######..................
...#######..................
...#######..................
..########..########........
..####################......
..######################....
..#######################...
..########################..
..########################..
..#########################.
..########.......##########.
.....##...........#########.
...................#########
....................########
....................########
....................########
....................########
....................########
....................########
...#...............#########
..####............#########.
.########.......###########.
.##########################.
.#########################..
#########################...
.#######################....
..#####################.....
....#################.......
........##########..........
)",
R"(
....................######...
...............###########...
............###############..
..........#################..
........###################..
.......####################..
......####################...
.....################........
....############.............
...###########...............
...#########.................
..#########..................
..########...................
.########....................
.########....................
.#######....########.........
.#######..#############......
#########################....
##########################...
###########################..
###########################..
############......##########.
##########.........#########.
#########...........#########
########.............########
########.............########
########.............########
.#######.............########
.#######.............########
.########...........#########
.#########.........#########.
..##########.....###########.
...#########################.
...########################..
....######################...
.....####################....
......##################.....
........##############.......
...........########..........
)",
R"(
###########################.
############################
############################
############################
############################
###########################.
#######...........#########.
#######...........#########.
#######..........#########..
#######..........#########..
#######..........########...
#######.........#########...
#######.........########....
#######........#########....
.#####.........#########....
...............########.....
..............#########.....
..............########......
.............#########......
.............########.......
............#########.......
............#########.......
............########........
...........#########........
...........########.........
..........#########.........
..........########..........
..........########..........
.........########...........
.........########...........
........#########...........
........########............
........########............
.......########.............
.......########.............
.......#######..............
.......#######..............
.........#####..............
)",
R"(
..........#########.........
........#############.......
......#################.....
.....###################....
....#####################...
...#######################..
...#######################..
...#########.....#########..
..#########.......#########.
..########.........########.
..########.........########.
..########.........########.
..########.........########.
..########.........########.
...########.......########..
...#########.....#########..
....#####################...
.....###################....
......#################.....
......#################.....
....#####################...
...#######################..
..#########......##########.
.########..........########.
.########..........#########
########............########
########............########
########............########
########............########
#########..........#########
#########..........#########
.##########......##########.
.##########################.
..########################..
..########################..
...######################...
....####################....
......################......
.........##########.........
)",
R"(
.........#########..........
.......#############........
.....#################......
....###################.....
...#####################....
..#######################...
.########################...
.##########......#########..
.#########........#########.
#########..........########.
########............#######.
########............#######.
########............########
########............########
########............########
#########..........#########
.########.........##########
.##########......###########
..##########################
..##########################
...#########################
....########################
......############..########
........########....#######.
....................#######.
...................########.
...................########.
..................########..
.................#########..
...............##########...
.............############...
.........###############....
....###################.....
...###################......
...##################.......
...################.........
...##############...........
...############.............
....######..................
)",
R"(
...............####..
..............######.
............########.
...........##########
..........###########
.........############
........############.
.......###########...
......###########....
.....###########.....
.....##########......
....##########.......
....#########........
...#########.........
...########..........
..#########..........
..########...........
.#########...........
.########............
.########............
.########............
#########............
########.............
########.............
########.............
########.............
########.............
########.............
########.............
########.............
########.............
########.............
.########............
.########............
.########............
.#########...........
..########...........
..#########..........
..#########..........
...#########.........
...##########........
....#########........
.....#########.......
.....##########......
......###########....
.......###########...
........###########..
.........############
..........###########
...........##########
............#########
.............#######.
...............####..
.................#...
)",
R"(
..####...............
.######..............
#########............
##########...........
###########..........
############.........
.############........
...###########.......
....###########......
.....##########......
......##########.....
.......##########....
........#########....
.........#########...
..........########...
..........#########..
...........########..
...........#########.
............########.
............########.
............########.
............#########
.............########
.............########
.............########
.............########
.............########
.............########
.............########
.............########
.............########
.............########
............########.
............########.
............########.
...........#########.
...........########..
..........#########..
..........#########..
.........#########...
........##########...
.......##########....
.......#########.....
.....###########.....
....###########......
...###########.......
..###########........
############.........
###########..........
##########...........
#########............
.#######.............
..####...............
...#.................
)",
R"(
...........#####...........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
###########################
###########################
###########################
###########################
###########################
###########################
###########################
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
..........#######..........
...........#####...........
)",
R"(
.##########################.
############################
############################
############################
############################
############################
.###########################
)",
R"(
...........####...........
..........######..........
..........#######.........
.........########.........
.........########.........
..........#######.........
..........######..........
.#####....######.....####.
.######...######...#######
#########..#####.#########
###############.##########
##########################
##########################
.########################.
......##############......
.........########.........
........###########.......
.......#############......
.....########.#######.....
....########..########....
....########..#########...
...########....########...
...########....########...
....#######.....#######...
.....#####......######....
......###.........##......
)",
R"(
......................####..
......................######
.....................#######
.....................#######
....................########
....................#######.
...................########.
...................#######..
..................########..
..................#######...
.................########...
.................#######....
................########....
................########....
................#######.....
...............########.....
...............#######......
..............########......
..............#######.......
.............########.......
.............#######........
............########........
............#######.........
...........########.........
...........#######..........
..........########..........
..........#######...........
..........#######...........
.........########...........
.........#######............
........########............
........#######.............
.......########.............
.......#######..............
......########..............
......#######...............
.....########...............
.....#######................
....########................
....#######.................
...########.................
...########.................
...#######..................
..########..................
..#######...................
.########...................
.#######....................
########....................
#######.....................
.######.....................
..#####.....................
)",
};
int Signpos[16][2] = {  //字符在原来尺寸下的左上角的位置
    { 7, 4 },
    { 7, 6 },
    { 7, 5 },
    { 7, 5 },
    { 7, 4 },
    { 8, 5 },
    { 7, 5 },
    { 8, 6 },
    { 7, 5 },
    { 7, 5 },
    { 4, 8 },
    { 4, 9 },
    { 13, 6 },
    { 23, 5 },
    { 7, 6 },
    { 3, 5 }
};
Image randomset[16][maxitertime];
#pragma endregion This is the image data

Image cover(const Image& img1, const Image& img2, int sx, int sy)
{
    Image imgans(img1);
    for (int i = max(0, -sx); i < min(img2.sx, img1.sx - sx); i++)
    {
        for (int j = max(0, -sy); j < min(img2.sy, img1.sy - sy); j++)
        {
            imgans[i + sx][j + sy] = img2[i][j];
        }
    }
    return imgans;
}

//对图片进行降噪处理
Image reducenoice(const Image& img)
{
    Image tmp(img);  //对img进行复制
    auto valid = [&](int x, int y) {return x >= 0 and x < img.sx and y >= 0 and y < img.sy; };  //判定访问是否越界
    int dx[] = { 0, 1, 1, 0, -1, -1, -1, 0, 1 };
    int dy[] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };   //创建遍历列表
    for (int x = 0; x < img.sx; x++) {
        for (int y = 0; y < img.sy; y++) {  //对每个x, y进行循环
            int cnt1 = 0, cnt2 = 0;  //cnt1为周围1圈黑点数量,cnt2为周围1圈点的数量
            for (int k = 0; k < 9; k++) {  //遍历周围的点
                int nx = x + dx[k], ny = y + dy[k];
                if (valid(nx, ny))
                    cnt2++, cnt1 += img[nx][ny];
            }
            tmp[x][y] = (cnt1 * 2 > cnt2);  //判定周围黑点数量是否比白点多,若是则将此处涂黑
        }
    }
    return tmp;
}

Image reducesize(const Image& img)
{
    const auto& rng = img.getminmaxrange();
    int minx, maxx, miny, maxy;
    tie(minx, maxx, miny, maxy) = rng;
    return img.subImage(minx, maxx, miny, maxy);
}

//将大于minsize大小的联通块分离出来
vector<Image> split(Image img, int minsize = 15)
{
    vector<Image> res;
    queue<pair<int, int>> q;  //存储连通块中的黑点
    int minx = INT_MAX, maxx = INT_MIN, miny = INT_MAX, maxy = INT_MIN;
    auto init = [&]() {minx = INT_MAX, maxx = INT_MIN, miny = INT_MAX, maxy = INT_MIN; };  //初始化变量
    auto valid = [&](int x, int y) { return x >= 0 and x < img.sx and y >= 0 and y < img.sy; };  //判定访问是否越界
    auto bfs = [&](int x, int y) {  //将当前连通块中所有黑点全部加入到队列q中
        queue<pair<int, int>> q1;
        int dx[] = { 0, 1, 0, -1 }, dy[] = { 1, 0, -1, 0 };
        q1.push({ x, y }); q.push(q1.back());
        chkmax(maxx, x), chkmax(maxy, y), chkmin(minx, x), chkmin(miny, y);
        img[x][y] = 0;
        while (!q1.empty()) {
            auto h = q1.front();
            int x = h.first, y = h.second;
            q1.pop();
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (valid(nx, ny) and img[nx][ny]) {
                    q1.push({ nx, ny }); q.push(q1.back());
                    img[nx][ny] = 0;
                    chkmax(maxx, nx), chkmax(maxy, ny), chkmin(minx, nx), chkmin(miny, ny);
                }
            }
        }
    };
    for (int j = 0; j < img.sy; j++) {
        for (int i = 0; i < img.sx; i++) {  //遍历整张图,注意变量枚举顺序
            if (img[i][j]) {  //如果此处存在黑点
                init();
                bfs(i, j);  //得到与它相连的连通块
                if (q.size() >= minsize) {  //这里的判断是防止有些噪点未被处理干净,被当为单个字符分离出去
                    Image tmpimg(maxx - minx + 1, maxy - miny + 1, img.conv1, img.conv2);  //依据大小构建子图
                    while (!q.empty()) {
                        auto p = q.front(); q.pop();
                        int x = p.first, y = p.second;
                        tmpimg[x - minx][y - miny] = 1;  //将队列里的黑点放到子图的相应位置
                    }
                    res.push_back(tmpimg);  //将子图放到列表里面
                }
                while (!q.empty()) q.pop();
            }
        }
    }
    return res;
}

//对图像进行线性拉伸变换
Image resize(const Image& img, int nx, int ny)
{
    Image res(nx, ny);
    /*vector<vector<double>> cnt;
    for (int i = 1; i <= nx; i++)
        cnt.push_back(vector<double>(ny));*/
    auto valid = [=](int x, int y) { return x >= 0 and x < nx and y >= 0 and y < ny; };
    double cx = double(nx) / img.sx, cy = double(ny) / img.sy;
    if (cx < 1)
    {
        for (int i = 0; i < img.sx; i++)
        {
            if (cy < 1)
            {
                for (int j = 0; j < img.sy; j++)
                {
                    if (img[i][j])
                        res[int(i * cx)][int(j * cy)] = 1;
                }
            }
            else
            {
                for (int j = 0; j < ny; j++)
                {
                    if (img[i][int(j / cy)])
                        res[int(i * cx)][j] = 1;
                }
            }
        }
    }
    else
    {
        for (int i = 0; i < nx; i++)
        {
            if (cy < 1)
            {
                for (int j = 0; j < img.sy; j++)
                {
                    if (img[int(i / cx)][j])
                        res[i][int(j * cy)] = 1;
                }
            }
            else
            {
                for (int j = 0; j < ny; j++)
                {
                    if (img[int(i / cx)][int(j / cy)])
                        res[i][j] = 1;
                }
            }
        }
    }
    return res;
}

//对图像进行旋转变换
Image rotate(const Image& img, double angle)
{
    angle = angle / 180 * pi;
    double minx = 1e5, maxx = -1e5, miny = 1e5, maxy = -1e5;
    vector<pair<double, double>> points;
    auto singlerotate = [](pair<double, double> p, double angle)
    {
        double x = p.second, y = -p.first;
        double nx = x * cos(angle) - y * sin(angle), ny = x * sin(angle) + y * cos(angle);
        return pair<double, double>({ -ny, nx });
    };
    for (int i = 0; i < img.sx; i++)
    {
        for (int j = 0; j < img.sy; j++)
        {
            if (img[i][j])
            {
                points.push_back({ i, j });

            }
        }
    }
    for (auto& i : points)
    {
        i = singlerotate(i, angle);
        chkmax(maxx, i.first);
        chkmin(minx, i.first);
        chkmax(maxy, i.second);
        chkmin(miny, i.second);
    }
    Image res(int(maxx - minx + 1), int(maxy - miny + 1));
    for (const auto& i : points)
    {
        int x = (i.first - minx), y = (i.second - miny);
        res[x][y] = 1;
    }
    return res;

}

double operator==(const Image& img1, const Image& img2)
{
    int bx = img2.sx, by = img2.sy;
    double cnt = 0;
    for (int i = 0; i < img1.sx; i++)
    {
        for (int j = 0; j < img1.sy; j++)
        {
            cnt += (img2[i][j] == img1[i][j]) ? 1 : 0;
        }
    }
    return (cnt / double(img1.sx * img1.sy));
}

Image balance(const Image& img)
{
    Image res(65, 38);
    int sumx = 0, sumy = 0, sumc = 0;
    for (int i = 0; i < img.sx; i++)
    {
        for (int j = 0; j < img.sy; j++)
        {
            if (img[i][j])
            {
                sumx += i, sumy += j, sumc++;
            }
        }
    }
    int cx = (2 * sumx + sumc) / (2 * sumc), cy = (2 * sumy + sumc) / (2 * sumc);  //确定中心位置
    int px = 32 - cx, py = 19 - cy;  //确定偏移量
    res = cover(res, img, px, py);  //进行平移
    return res;
}
char recognize(const Image& img)
{
    double rate = 0;
    int idx = -1;  
    Image imgtmp = balance(img);  //调整img的位置
#ifdef debug
    cout << "Balancing the input :\n" << imgtmp << endl;
#endif // debug

    for (int i = 0; i < maxitertime; i++)
    {
        for (int j = 0; j < 16; j++)
        {
            double r = (imgtmp == randomset[j][i]);  //比较相似程度
            if (r > rate)  //如果匹配度最大
            {
                rate = r;  //记录匹配度
                idx = j;  //记录编号
            }
        }
    }
    return convtable[idx];  //返回对应的字符
}
int t, n, m;
Image test;
vector<Image> test2, signset;
int calc(const string& s)
{
    vector<int> stk1;
    vector<char> stk2;
    int optnum = 0, isd = 0;
    auto cal = [&](char c)
    {
        int rhs = stk1.back();
        stk1.pop_back();
        int lhs = stk1.back();
        stk1.pop_back();
        switch (c)
        {
        case '+':
            stk1.push_back(lhs + rhs);
            break;
        case '-':
            stk1.push_back(lhs - rhs);
            break;
        case '*':
            stk1.push_back(lhs * rhs);
            break;
        case '/':
            stk1.push_back(lhs / rhs);
            break;
        }
    };
    auto level = [](char c)
    {
        switch (c)
        {
        case '(':
            return 0;
            break;
        case '+':
        case '-':
            return 1;
            break;
        case '*':
        case '/':
            return 2;
            break;
        }
        return -1;
    };
    for (const auto c : s)
    {
        if (c >= '0' and c <= '9')
        {
            optnum = 10 * optnum + (c - 48);
            isd = 1;
        }
        else
        {
            if (isd)
            {
                stk1.push_back(optnum);
                isd = 0;
                optnum = 0;
            }

            if (stk2.empty())
            {
                stk2.push_back(c);
            }
            else
            {
                switch (c)
                {
                case '(':
                    stk2.push_back(c);
                    break;
                case ')':
                    while (stk2.back() != '(')
                    {
                        cal(stk2.back());
                        stk2.pop_back();
                    }
                    stk2.pop_back();
                    break;
                default:
                    while (!stk2.empty() and level(c) <= level(stk2.back()))
                    {
                        cal(stk2.back());
                        stk2.pop_back();
                    }
                    stk2.push_back(c);
                }
            }
        }
    }
    if (isdigit(s.back()))
    {
        stk1.push_back(optnum);
    }
    while (!stk2.empty())
    {
        cal(stk2.back());
        stk2.pop_back();
    }
    return stk1.back();
}

void init(int t)
{
    for (int i = 0; i < 16; i++)
    {
        Signset[i] = cover(Image(65, 38), Signset[i], Signpos[i][0], Signpos[i][1]);  //这里我为了减少代码长度,只截取了各个字符的黑色区域,保存了它们的左上角坐标,这一步做的就是还原他们原来所在的位置
#ifdef debug
        cout << "char " << convtable[i] << ":\n" << Signset[i] << endl;
#endif // debug

    }
    double Mmin = 0.9, Mmax = 1, Rmin, Rmax, Smin, Smax;
    if (0 <= t and t < 30)
    {
        Rmin = -2, Rmax = 2, Smin = Smax = 0;
    }
    else if (30 <= t and t < 90)
    {
        Rmin = -10, Rmax = 10, Smin = -0.1, Smax = 0.1;
    }
    else
    {
        Rmin = -15, Rmax = 15, Smin = -0.1, Smax = 0.1;
    }
    uniform_real_distribution M(Mmin, Mmax), R(Rmin, Rmax), S(Smin, Smax);  //初始化随机数生成器
    default_random_engine gen(time(NULL));  //初始化随机数生成器
    auto trans = [&](const Image& img, double M, double Mx, double My, double R, double Sx, double Sy)
    {
        auto valid = [=](int x, int y) { return x >= 0 and x < 65 and y >= 0 and y < 38; };  //判断是否越界
        Image imgans(65, 38);
        for (int i = 0; i < 65; i++)
        {
            for (int j = 0; j < 38; j++)
            {
                if (img[i][j])
                {
                    double y = i + 0.5 - 32.5, x = j + 0.5 - 19;  //先对像素点进行平移,方便后面的旋转,扭曲操作
                    x *= M * Mx, y *= M * My;  //进行横向/纵向伸缩变换
                    double nx = x * cos(torad(R)) - y * sin(torad(R)), ny = x * sin(torad(R)) + y * cos(torad(R));  //进行旋转变换
                    x = nx + Sy * ny, y = ny + Sx * nx;  //进行扭曲变换
                    int X = int(round(y + 32.5 - 0.5)), Y = int(round(x + 19 - 0.5));  //最终的X, Y坐标
                    if (valid(X, Y))
                    {
                        imgans[X][Y] = 1;
                    }
                }
            }
        }
        return balance(reducenoice(imgans));  //对变换后的图像降噪后再摆正位置
    };
    for (int i = 0; i < 16; i++)  //枚举每个字符
    {
#ifdef debug
        cout << "Generating data from char " << convtable[i] << " :\n";
#endif // debug

        for (int j = 0; j < maxitertime; j++)  //在参数范围内随机生成至多maxitertime张图像,这里的数字为75
        {
            double _M = M(gen), _Mx = M(gen), _My = M(gen), _R = R(gen), _Sx = S(gen), _Sy = S(gen);  //获取随机数
            randomset[i][j] = trans(Signset[i], _M, _Mx, _My, _R, _Sx, _Sy);  //生成随机图像
#ifdef debug
            cout << "M = " << _M << ", Mx = " << _Mx << ", My = " << _My << ", R = " << _R << ", Sx = " << _Sx << ", Sy = " << _Sy << endl;
            cout << randomset[i][j] << endl << endl;
#endif // debug
        }
    }
}

int main()
{

#if runmode == 1
    ifstream in("font.txt");
    freopen("data.out", "w", stdout);
    cout << "vector<Image> Signset{\n";
    for (int i = 0; i < 16; i++)
    {
        string tmp, tmp2;
        for (int j = 1; j <= 65; j++)
        {
            in >> tmp2;
            tmp += tmp2 + '\n';
        }
        auto img = Image(tmp);
        signset.push_back(reducesize(img));
        cout << "R\"(\n" << reducesize(img) << ")\", \n";
        tie(Signpos[i][0], Signpos[i][1], ignore, ignore) = img.getminmaxrange();
    }
    cout << "};\n";
    cout << "int Signpos[16][2] = \n{\n";
    for (int i = 0; i < 16; i++)
    {
        cout << "   { " << Signpos[i][0] << ", " << Signpos[i][1] << " },\n";
    }
    cout << "};\n";

#endif

#if runmode == 2
#ifndef ONLINE_JUDGE
    freopen("data.out", "w", stdout);
#endif
    init(10);
#endif

#if runmode == 3
#ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    string tmp1, tmp2;
    cin >> t >> n >> m;
    init(t);  //根据数据编号初始化
    while (cin >> tmp2)
    {
        tmp1 += tmp2 + ' ';
    }
    test = Image(tmp1);  //从字符串构造图像
    test = reducenoice(test);  //对图像进行降噪
    //cout << test << endl << endl;
    test2 = split(test);  //分离单个字符
    tmp2.clear();
    for (const auto& i : test2)
    {
        //cout << i << endl << endl;
        tmp2 += recognize(i);  //分别对列表中的字符逐一识别,并加到字符串中
    }

#ifndef ONLINE_JUDGE
    cout << tmp2 << endl << calc(tmp2);
#else
    cout << calc(tmp2) << endl;  //最后调用计算函数,完成计算(一定要加换行符啊!)
#endif
#endif
}

后记:

这道题的确是一道非常有挑战性的题目,我花费了将近一周,尝试了不同的方法,最终,在我代码自带的大常数下,以每点最高900ms的速度通过了此题。感谢之前为了A掉此题而不断尝试的人,同时感谢为我提供了一定思路的 @UIKIt 的题解,尽管我按照他的想法最终只拿了430pts

这一题在洛谷上没有一份C++代码过题的历史,终于结束了。