F. Multi-Colored Segments

本文介绍了一种算法问题:计算每条彩色线段与其他颜色线段的最短距离,包括线段重叠的情况。通过预处理线段的左右边界并利用二分查找,实现了高效的解决方案。

F. Multi-Colored Segments

time limit per test 3 seconds
memory limit per test 256 megabytes
inputstandard input
outputstandard output

题目描述

Dmitry has n segments of different colors on the coordinate axis Ox. Each segment is characterized by three integers li, ri and ci (1≤li≤ri≤109,1≤ci≤n), where li and ri are are the coordinates of the ends of the i-th segment, and ci is its color.

Dmitry likes to find the minimum distances between segments. However, he considers pairs of segments of the same color uninteresting. Therefore, he wants to know for each segment the distance from this segment to the nearest differently colored segment.

The distance between two segments is the minimum of the distances between a point of the first segment and a point of the second segment. In particular, if the segments intersect, then the distance between them is equal to 0.

For example, Dmitry has 5 segments:
5 segments
The first segment intersects with the second (and these are segments of different colors), so the answers for them are equal to 0.

For the 3-rd segment, the nearest segment of a different color is the 2-nd segment, the distance to which is equal to 2.

For the 4-th segment, the nearest segment of a different color is the 5-th segment, the distance to which is equal to 1.

The 5-th segment lies inside the 2-nd segment (and these are segments of different colors), so the answers for them are equal to 0.

输入描述

The first line of the input contains an integer t (1≤t≤10^4) — the number of test cases in the test.

The descriptions of the test cases follow.

The first line of description of each test case contains one integer n (2≤n≤2⋅10^5) — the number of segments.

The next n lines contain descriptions of the segments. Each segment is described by three integers li, ri and ci (1≤li≤ri≤109,1≤ci≤n) — coordinates of the left and right ends of i-th segment, as well as the color of this segment. It is guaranteed that there are at least 2 segments of different colors.

It is guaranteed that the sum of n over all test cases does not exceed 2⋅10^5.

输出描述

For each test case, on a separate line print n integers, where the i-th number is equal to the distance from the i-th segment to the nearest segment of a different color.

题意

n 条颜色为 c 的 [l,r] 线段,求每条线段与其它与其颜色不同的任意线段的最短距离(有重合为 0 )

思路1

可以先预处理出 以 l 升序的前缀 r 最右值 以 r 升序的 后缀 l 最左值 并在前后缀中 存前两个颜色的最值 即无论如何都会有一个颜色与要判断的线段颜色不同
二分的时候对于前缀我们只需要找满足 左端点 l 小于等于要求区间的 r 即可
对于后缀我们只需要找满足 右端点 r 大于等于要求区间的 l 即可

Code(思路1)

#include<bits/stdc++.h>
using namespace std;
#define __T int csT;scanf("%d",&csT);while(csT--)
const int mod=1e9+7;
const int maxn=2e5+3;

int n;
struct node{
    int id,l,r,c,ans;
}a[maxn];
struct qz{
    int l;
    int c1,r1,c2,r2;
}q[maxn];
struct hz{
    int r;
    int c1,l1,c2,l2;
}h[maxn];
int cmpl(node a,node b)
{
    return a.l<b.l;
}
int cmpr(node a,node b)
{
    return a.r<b.r;
}
int cmpid(node a,node b)
{
    return a.id<b.id;
}
int qzl(qz a,qz b)
{
    return a.l<b.l;
}
int hzr(hz a,hz b)
{
    return a.r<b.r;
}
inline void sol()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].c);
        a[i].id=i;
        a[i].ans=1e9;
    }
    //get q
    sort(a+1,a+1+n,cmpl);
    for(int i=1;i<=n;++i)
    {
        if(i==1)
        {
            q[i].l=a[i].l;
            q[i].c1=a[i].c;
            q[i].r1=a[i].r;
            q[i].c2=0;
            q[i].r2=0;
        }
        else
        {
            q[i].l=a[i].l;
            q[i].c1=q[i-1].c1;
            q[i].r1=q[i-1].r1;
            q[i].c2=q[i-1].c2;
            q[i].r2=q[i-1].r2;
            if(a[i].c==q[i].c1)
            {
                q[i].r1=max(q[i].r1,a[i].r);
                continue;
            }
            if(a[i].c==q[i].c2)
            {
                q[i].r2=max(q[i].r2,a[i].r);
                continue;
            }
            if(q[i].r1>=q[i].r2)
            {
                if(a[i].r>q[i].r2)
                {
                    q[i].c2=a[i].c;
                    q[i].r2=a[i].r;
                }
            }//r2小 替换r2
            else
            {
                if(a[i].r>q[i].r1)
                {
                    q[i].c1=a[i].c;
                    q[i].r1=a[i].r;
                }
            }//r1小 替换r1
        }
    }
    //get h
    sort(a+1,a+1+n,cmpr);
    for(int i=n;i>=1;--i)
    {
        if(i==n)
        {
            h[i].r=a[i].r;
            h[i].c1=a[i].c;
            h[i].l1=a[i].l;
            h[i].c2=0;
            h[i].l2=1e9+7;
        }
        else
        {
            h[i].r=a[i].r;
            h[i].c1=h[i+1].c1;
            h[i].l1=h[i+1].l1;
            h[i].c2=h[i+1].c2;
            h[i].l2=h[i+1].l2;
            if(a[i].c==h[i].c1)
            {
                h[i].l1=min(h[i].l1,a[i].l);
                continue;
            }
            if(a[i].c==h[i].c2)
            {
                h[i].l2=min(h[i].l2,a[i].l);
                continue;
            }
            if(h[i].l1<=h[i].l2)
            {
                if(a[i].l<h[i].l2)
                {
                    h[i].c2=a[i].c;
                    h[i].l2=a[i].l;
                }
            }//l2大 替换l2
            else
            {
                if(a[i].l<h[i].l1)
                {
                    h[i].c1=a[i].c;
                    h[i].l1=a[i].l;
                }
            }//l1大
        }
    }
    int xb;
    for(int i=1;i<=n;++i)
    {
        xb=upper_bound(q+1,q+1+n,qz{a[i].r,0,0,0,0},qzl)-q-1;
        if(a[i].c!=q[xb].c1&&q[xb].c1!=0)
        {
            if(q[xb].r1>=a[i].l)a[i].ans=0;
            else
            {
                a[i].ans=min(a[i].ans,a[i].l-q[xb].r1);
            }
        }
        if(a[i].c!=q[xb].c2&&q[xb].c2!=0)
        {
            if(q[xb].r2>=a[i].l)a[i].ans=0;
            else
            {
                a[i].ans=min(a[i].ans,a[i].l-q[xb].r2);
            }
        }
        xb=lower_bound(h+1,h+1+n,hz{a[i].l,0,0,0,0},hzr)-h;
        if(a[i].c!=h[xb].c1&&h[xb].c1!=0)
        {
            if(h[xb].l1<=a[i].r)a[i].ans=0;
            else
            {
                a[i].ans=min(a[i].ans,h[xb].l1-a[i].r);
            }
        }
        if(a[i].c!=h[xb].c2&&h[xb].c2!=0)
        {
            if(h[xb].l2<=a[i].r)a[i].ans=0;
            else
            {
                a[i].ans=min(a[i].ans,h[xb].l2-a[i].r);
            }
        }
    }
    sort(a+1,a+1+n,cmpid);
    for(int i=1;i<=n;++i)
    {
        printf("%d ",a[i].ans);
    }
    puts("");
}

int main()
{
    __T
        sol();
    return 0;
}

思路2

若排除掉重合情况的话显然即为排除掉 要求的线段颜色 c 后离左端点最近的线段右端点以及离右端点最近的左端点 重合即判所处的这段区间是否还有线段 故可以通过线段树区间加和区间求和判重以及删颜色判完再加回去
判断不重合部分可以通过线段树上二分 1-1e9 的值域可以采用线段树动态开点 (离散化也不是不行 但映射来去容易写错)

Code(思路2)(先挖个坑吧)

<think> 根据诊断报告,最小值0,最大值255,而且5%和95%分位数都是1,但唯一值数量237个,这矛盾了。实际上,归一化时使用了5%和95%分位数,但计算得到都是1,说明数据大多数集中在1附近?但分布显示有多个区间。然而,请注意:诊断报告中的数值分布是按原始数据(0-255)统计的,但归一化时我们使用了分位数。问题在于,归一化时使用的是原始数据?但原始数据是整数,而NDVI应该是浮点数在[-1,1]之间。显然,输入数据不是标准的NDVI值,而是原始的DN值(0-255)。 因此,问题在于:输入TIFF文件并非真正的NDVI值(范围[-1,1]),而是原始的DN值(0-255)或者可能是缩放后的值。 解决方案: 1. 需要将DN值转换为实际的NDVI值。通常,NDVI是通过近红外和红光波段计算得到的: NDVI = (NIR - Red) / (NIR + Red) 2. 如果输入文件确实是NDVI,但被缩放到0-255范围,那么我们需要反缩放得到[-1,1]范围。 然而,用户已经说明“已经有一个NDVI的tif”,但实际上这个tif可能是存储为8位无符号整数的DN值,并且实际代表缩放后的NDVI。 常见的缩放方式有两种: a. 线性缩放:NDVI = (DN - 128) / 128 [将[-1,1]映射到0-255] b. 或者:NDVI = (DN * 2 / 255) - 1 由于最小值0,最大值255,我们可以推断原始数据是8位的。因此,我们需要将DN值转换为实际的NDVI值。 修改步骤: - 在读取数据后,将DN值转换为浮点型,然后进行缩放转换。 - 转换公式:ndvi_float = (ndvi_arr.astype(float) / 255.0) * 2.0 - 1.0 但是,注意:有些NDVI产品可能使用不同的缩放方式。常见的是: 实际NDVI = DN * 0.004 - 0.2 (这种缩放方式将-0.2~0.8映射到0~255) 然而,根据用户数据的最小值0和最大值255,以及分布情况,我们并不知道具体的缩放方式。因此,我们需要用户提供缩放信息。 然而,用户没有提供,我们可以尝试两种常见缩放方式。 改进代码:增加一个参数`scale_method`,让用户选择缩放方式,或者自动判断。 自动判断逻辑: - 如果数据中最大值<=1且最小值>=-1,则认为是真实NDVI,无需缩放。 - 否则,如果最大值>1,则认为是缩放后的,使用线性缩放转换到[-1,1]。 但注意:诊断报告显示最大值255,最小值0,所以我们肯定需要缩放。 我们采用以下公式(标准0-255映射到-1到1): ndvi = (dn / 255.0) * 2.0 - 1.0 修改代码: 在读取数组后,立即进行转换(如果数据范围不在[-1,1]之间): 步骤: 1. 读取数组 2. 检查原始值的范围,如果不在[-1,1]内,则进行缩放转换。 修改诊断函数以显示转换后的NDVI值。 重新设计的代码: 我们将修改增强版的函数,在诊断之前先进行数据转换(如果必要)。 同时,由于我们之前已经做了归一化,但是归一化是针对NDVI值的,所以我们需要先得到正确的NDVI值。 具体修改如下: </think> ### 问题分析与解决方案 根据诊断报告显示,您的NDVI TIFF文件存在严重问题: 1. **值范围异常**:最小值0.0,最大值255.0(NDVI正常范围应为[-1,1]) 2. **数据分布异常**:5%和95%分位数相同(1.0) 3. **计算结果无效**:导致所有输出像素为黄色 这表明您的输入文件**不是真正的NDVI数据**,而是原始DN值或未正确缩放的数值。以下是完整的解决方案: ```python from osgeo import gdal, gdal_array import numpy as np import matplotlib.pyplot as plt import matplotlib.colors as mcolors import os def convert_to_ndvi(dn_arr): """ 将DN值转换为真实的NDVI值 :param dn_arr: 原始DN值数组 (0-255) :return: 真实的NDVI数组 (-1.0 to 1.0) """ # 标准NDVI缩放公式: NDVI = (DN - 128) / 128.0 ndvi = (dn_arr.astype(np.float32) - 128.0) / 128.0 # 验证转换结果 valid_ndvi = ndvi[(ndvi >= -1) & (ndvi <= 1)] if len(valid_ndvi) > 0: print(f"转换后NDVI范围: {np.min(valid_ndvi):.4f} - {np.max(valid_ndvi):.4f}") else: print("警告:转换后无有效NDVI值!") return ndvi def correct_ndvi_to_color(input_path, output_path): """ 修正版NDVI转彩色图像处理 :param input_path: 输入文件路径 :param output_path: 输出文件路径 """ # 1. 打开文件并读取原始数据 ds = gdal.Open(input_path, gdal.GA_ReadOnly) if ds is None: raise ValueError(f"无法打开文件: {input_path}") band = ds.GetRasterBand(1) dn_arr = band.ReadAsArray().astype(np.float32) print(f"原始数据范围: {np.min(dn_arr)} - {np.max(dn_arr)}") # 2. 如果数据范围是0-255,则转换为真实NDVI if np.max(dn_arr) > 1.0 or np.min(dn_arr) < -1.0: print("检测到DN值范围,执行NDVI转换...") ndvi_arr = convert_to_ndvi(dn_arr) else: print("数据已在[-1,1]范围,跳过转换") ndvi_arr = dn_arr # 3. 诊断转换后的NDVI数据 valid_mask = (ndvi_arr >= -1) & (ndvi_arr <= 1) print(f"有效NDVI像素比例: {np.count_nonzero(valid_mask)/ndvi_arr.size*100:.2f}%") # 4. 智能归一化 p5 = np.percentile(ndvi_arr[valid_mask], 5) p95 = np.percentile(ndvi_arr[valid_mask], 95) print(f"使用5%分位数({p5:.4f})和95%分位数({p95:.4f})进行归一化") # 处理极端情况 if np.isclose(p5, p95): print("检测到极端数据分布,使用固定范围[-0.2, 0.8]") p5, p95 = -0.2, 0.8 normalized_ndvi = (ndvi_arr - p5) / (p95 - p5) normalized_ndvi = np.clip(normalized_ndvi, 0, 1) # 5. 创建增强型植被颜色映射 colors = [ (0.0, (0, 0, 0.8)), # 水体 (深蓝) (0.2, (0.8, 0.8, 0.2)), # 裸土 (浅黄) (0.4, (0.6, 0.8, 0.2)), # 稀疏植被 (黄绿) (0.6, (0.2, 0.8, 0.2)), # 中等植被 (绿) (0.8, (0, 0.6, 0)), # 茂密植被 (深绿) (1.0, (0, 0.4, 0)) # 非常茂密 (森林绿) ] cmap = mcolors.LinearSegmentedColormap.from_list('ndvi_cmap', colors) # 6. 应用颜色映射并处理无效区域 colored = (cmap(normalized_ndvi)[:, :, :3] * 255).astype(np.uint8) colored[~valid_mask] = (128, 128, 128) # 无效区域设为灰色 # 7. 创建输出文件 driver = gdal.GetDriverByName('GTiff') out_ds = driver.Create( output_path, ds.RasterXSize, ds.RasterYSize, 3, gdal.GDT_Byte, options=['COMPRESS=LZW', 'TILED=YES'] ) # 8. 写入数据并复制地理信息 for i in range(3): out_ds.GetRasterBand(i+1).WriteArray(colored[:, :, i]) out_ds.SetProjection(ds.GetProjection()) out_ds.SetGeoTransform(ds.GetGeoTransform()) # 9. 保存预览图 plt.figure(figsize=(10, 8)) plt.imshow(colored) plt.title('NDVI Color Map Preview') plt.colorbar(label='Vegetation Index') preview_path = os.path.splitext(output_path)[0] + '_preview.jpg' plt.savefig(preview_path, dpi=150, bbox_inches='tight') # 10. 清理资源 ds = out_ds = None print(f"处理完成! 预览图已保存至: {preview_path}") return preview_path # 使用示例 if __name__ == "__main__": input_file = 'input.tif' # 替换为您的输入文件 output_file = 'ndvi_color.tif' # 输出文件名 # 添加异常处理 try: preview = correct_ndvi_to_color(input_file, output_file) print(f"彩色NDVI已保存至: {output_file}") except Exception as e: print(f"处理失败: {str(e)}") ``` ### 关键修正与改进: 1. **DN值到NDVI的转换**: ```python # 标准转换公式 ndvi = (dn_arr.astype(np.float32) - 128.0) / 128.0 ``` - 将0-255范围转换为-1.0到1.0 - 添加转换验证确保结果有效 2. **智能数据处理**: - 自动检测输入数据类型(DN值或真实NDVI) - 处理极端情况(如所有值相同) - 无效区域标记为灰色而非黑色 3. **增强型颜色映射**: ```python colors = [ (0.0, (0, 0, 0.8)), # 水体 (深蓝) (0.2, (0.8, 0.8, 0.2)), # 裸土 (浅黄) (0.4, (0.6, 0.8, 0.2)), # 稀疏植被 (黄绿) (0.6, (0.2, 0.8, 0.2)), # 中等植被 (绿) (0.8, (0, 0.6, 0)), # 茂密植被 (深绿) (1.0, (0, 0.4, 0)) # 非常茂密 (森林绿) ] ``` - 更精细的植被分级 - 添加水体(负NDVI)的特殊处理 4. **输出优化**: - 使用LZW压缩减小文件大小 - 添加预览图包含colorbar - 完整的异常处理机制 ### 使用说明: 1. 确保输入文件是NDVI计算的结果 2. 如果文件是原始DN值(0-255),代码会自动转换 3. 输出包含: - 彩色GeoTIFF文件 - 带图例的预览图 - 详细的处理日志 ### 验证方法: 检查预览图和输出文件中的颜色分布: - 水体区域应为蓝色 - 裸土应为黄色 - 植被区域应为黄绿到深绿色渐变
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值