L3-018. 森森美图

一、题目

这里写图片描述这里写图片描述

二、个人理解

Tips:

  • 此题第一个难点就是读懂题目,当时费了挺长时间才知道样例是如何算出的。如下图所示,是其面部轮廓,其分数计算过程为
    score=1+2+2+9+1+2+221+2+921+2+1+1+1+1+121=3+172=27.04 s c o r e = 1 + 2 + 2 + 9 + 1 + ( 2 + 2 ) ∗ ( 2 − 1 ) + ( 2 + 9 ) ∗ ( 2 − 1 ) + 2 + 1 + 1 + 1 + ( 1 + 1 ) ∗ ( 2 − 1 ) = 3 + 17 ∗ 2 = 27.04
    这里写图片描述
  • 理解题意后就是基本就会了一半了,接下来主要是在A和B的直线的两边分别用BFS求出其最低分数和的曲线,最后相加便能得出结果(注意A和B只能计算一次,所以最后要减去A和B一次)。
  • 除了上述理解外,此题还有几个坑:
    1. 补充说明的第一点(即横轴向右为正,纵轴向下为正),要注意这里表示和程序中数组表示可能不同,要记得转化(如在我的程序中,只要将A和B的x、y相反输入即可);
    2. 因为补充说明的第一点(忽略正好位于连接A和B的直线(注意不是线段)上的像素点)。如何判断三点是否在一个直线上很关键,这里采用三角的行列式公式,即用面积表示,当面积等于0,则其共线;
    3. 还需记住,计算不同部分时,一定要重新初始化。

C++:

#include <iostream>
#include <queue>
#include <cmath>
#include <cstdio>
using namespace std;

#define inf 0x11111111;

struct Point {
    int x,y;
    double dis;
    //重载
    bool operator !=(const Point &c) const {
        return x!=c.x||y!=c.y;
    }
};
queue<Point> q;
int n,m;
double fdis[105][105];//记录从开始点到每个点的最小值
double score[105][105];//接收题目中各点的分数
Point A,B;//起点和终点
int xa,ya,xb,yb;//边界
int dir1[][2]= {{0,1},{1,0},{-1,0},{0,-1}}; //上下左右的方向
int dir2[][2]= {{-1,-1},{1,-1},{-1,1},{1,1}}; //斜的方向
bool tag;//tag=1表示上半部分,tag=0表示下半部分

//三角形行列式公式
int cross(Point a,Point b,Point p)
{
    return (b.x-a.x)*(p.y-a.y)-(p.x-a.x)*(b.y-a.y);
}

void inque(Point p)
{
    //有效值判断
    if(p.x<1||p.x>n||p.y<1||p.y>m) {
        return ;
    }
    //当有最小值更新,否则return
    if(p.dis>=fdis[p.x][p.y]) {
        return ;
    }
    if(tag) {
        //上半部分,但是包含起点和终点
        if(p!=A&&p!=B&&cross(A,B,p)<=0) {
            return ;
        }
    } else {
        //上半部分,但是包含起点和终点
        if(p!=A&&p!=B&&cross(A,B,p)>=0) {
            return ;
        }
    }
    fdis[p.x][p.y]=p.dis;//更新
    //到终点时,就不继续将其入队
    if(p.x==B.x&&p.y==B.y) {
        return;
    }
    q.push(p);
}

void bfs()
{
    //清空队列
    while(!q.empty()) {
        q.pop();
    }
    //初始化
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            fdis[i][j]=inf;
        }
    }
    inque(A);
    while(!q.empty()) {
        Point now=q.front();
        q.pop();
        Point next;
        //计算上下左右方向
        for(int i=0; i<4; i++) {
            next.x=now.x+dir1[i][0];
            next.y=now.y+dir1[i][1];
            next.dis=fdis[now.x][now.y]+score[next.x][next.y];
            inque(next);
        }
        //计算斜方向
        for(int i=0; i<4; i++) {
            next.x=now.x+dir2[i][0];
            next.y=now.y+dir2[i][1];
            next.dis=fdis[now.x][now.y]+score[next.x][next.y]+(score[next.x][next.y]+score[now.x][now.y])*(sqrt(2)-1);
            inque(next);
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            cin>>score[i][j];
            fdis[i][j]=inf;
        }
    }
    cin>>A.y>>A.x>>B.y>>B.x;
    A.x++;A.y++;B.x++;B.y++;//因为每从0开始,所以都加1,并且对其表示进行了转化
    A.dis=score[A.x][A.y];

    double ans=0;
    tag=false;bfs();ans+=fdis[B.x][B.y];//上半部分
    tag=true;bfs();ans+=fdis[B.x][B.y];//下半部分
    printf("%.2f\n",ans-score[A.x][A.y]-score[B.x][B.y]);//最后再减去重复的起点和终点
}
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值