本篇介绍的是基于Elasticsearch实现搜索推荐词,其中需要用到Elasticsearch的pinyin插件以及ik分词插件,代码的实现这里提供了java跟C#的版本方便大家参考。

1.实现的结果

①当搜索【qiy】的时候,能匹配企业、祈愿等

②当搜索【qi业】的时候,只能匹配的到企业,如果没有企业,将使用模糊查询,匹配祈愿。

③当搜索【q业】的时候结果同②。

④当搜索【企y】或【企ye】的时候结果同②。

④当搜索【qy】的时候,能匹配企业、祈愿等。

2.实现的逻辑

中文匹配前缀==》全拼匹配前缀==》拼音首字母匹配前缀==》拼音模糊匹配前缀

优先级从左到右,当前面三个有结果的时候不建议用模糊匹配,这样结果更加精确。比如需要获取8个推荐词,先获取中文的,如果足够8个将不再获取之后的匹配结果。但是当模糊匹配之前已经存在匹配结果了,即使数量没有达到8个,也不再继续获取模糊匹配结果。

3.插件准备

ik分词插件安装相对简单,网上教程也多,这里不做介绍。这里讲解下pinyin插件,官方版本的拼音插件不支持中文,处理结果只有拼音的,这样会出现同音字匹配,结果不准确。

这里感谢小伙伴分享的拼音插件修改方法:https://www.cnblogs.com/danvid/p/10691547.html

按照里面的操作处理后的插件将实现:

企业画报:{"qi","企","ye","业","hua","画","bao","报"}

拼音插件的各项具体属性参考:https://blog.csdn.net/a1148233614/article/details/80280024,里面有详细介绍。

4.Elasticsearch创建index

这里使用的ES版本为7.0.1,不再支持mapping,创建代码如下:

PUT /suggest_tset
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": {
"analyzer": {
"prefix_pinyin_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"prefix_pinyin"
]
},
"full_pinyin_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"full_pinyin"
]
},
"like_pinyin_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"like_pinyin"
]
}
},
"filter": {
"_pattern": {
"type": "pattern_capture",
"preserve_original": true,
"patterns": [
"([0-9])",
"([a-z])"
]
},
"prefix_pinyin": {
"type": "pinyin",
"keep_first_letter": "true",
"keep_full_pinyin": "false",
"none_chinese_pinyin_tokenize": "false",
"keep_separate_chinese": "true",
"keep_original": "false"
},
"full_pinyin": {
"type": "pinyin",
"keep_first_letter": "false",
"keep_full_pinyin": "true",
"keep_original": "false",
"keep_separate_chinese": "true",
"keep_none_chinese_in_first_letter": "false"
},
"like_pinyin": {
"type": "pinyin",
"keep_first_letter": "true",
"keep_full_pinyin": "true",
"keep_joined_full_pinyin": "false",
"keep_original": "false",
"keep_separate_chinese": "false",
"keep_none_chinese_in_first_letter": "false"
}
}
}
},
"mappings": {
"dynamic": "false",
"properties": {
"kwsuggest": {
"fields": {
"suggestText": {
"type": "completion",
"analyzer": "standard",
"preserve_separators": "false",
"preserve_position_increments": "true",
"max_input_length": 50
},
"prefix_pinyin": {
"type": "completion",
"analyzer": "prefix_pinyin_analyzer",
"search_analyzer": "standard",
"preserve_separators": "false"
},
"full_pinyin": {
"type": "completion",
"analyzer": "full_pinyin_analyzer",
"search_analyzer": "standard",
"preserve_separators": "false"
},
"like_pinyin": {
"type": "completion",
"analyzer": "like_pinyin_analyzer",
"preserve_separators": "false"
}
},
"type": "text"
}
}
}
}

 这里插入几条测试数据

POST _bulk/?refresh=true
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "企业规划"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "祈愿设计 完美无瑕"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "悬崖的图片 美景"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "县衙地址 那里呢"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "悬崖风景图"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "起夜的风光 真的美"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "起夜第二个词 测试使用"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "需要一半留下一半打一字谜"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "许亚为"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "许雅非测试"}
{ "index" : { "_index" : "suggest_tset", "_type" : "_doc" } }
{ "kwsuggest": "徐杨是谁"}

 

下面为测试的查询语句

GET /suggest_tset/_search
{
"suggest": {
"suggestText": {
"prefix": "qi业",
"completion": {
"field": "kwsuggest.suggestText",
"skip_duplicates": true
}
},
"full_pinyin": {
"prefix": "qi业",
"completion": {
"field": "kwsuggest.full_pinyin",
"skip_duplicates": true
}
},
"prefix_pinyin": {
"prefix": "qi业",
"completion": {
"field": "kwsuggest.prefix_pinyin",
"skip_duplicates": true
}
},
"like_pinyin": {
"prefix": "qi业",
"completion": {
"field": "kwsuggest.like_pinyin",
"skip_duplicates": true,
"fuzzy": {
"fuzziness": 1
}
}
}
}
}  

当输入查询条件为【qiy】的时候,结果为:

{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"full_pinyin" : [
{
"text" : "qiy",
"offset" : 0,
"length" : 3,
"options" : [
{
"text" : "起夜的风光 真的美",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "-jgnlHMBSEyTxFiDO4lU",
"_score" : 1.0,
"_source" : {
"kwsuggest" : "起夜的风光 真的美"
}
},
{
"text" : "起夜第二个词 测试使用",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "aDg3lHMBSEyTxFiDXprV",
"_score" : 1.0,
"_source" : {
"kwsuggest" : "起夜第二个词 测试使用"
}
}
]
}
],
"like_pinyin" : [
{
"text" : "qiy",
"offset" : 0,
"length" : 3,
"options" : [
{
"text" : "企业规划",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "9TgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "企业规划"
}
},
{
"text" : "祈愿设计 这是啥呢",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "9jgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "祈愿设计 这是啥呢"
}
},
{
"text" : "起夜的风光 真的美",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "-jgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜的风光 真的美"
}
},
{
"text" : "起夜第二个词 测试使用",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "aDg3lHMBSEyTxFiDXprV",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜第二个词 测试使用"
}
}
]
}
],
"prefix_pinyin" : [
{
"text" : "qiy",
"offset" : 0,
"length" : 3,
"options" : [ ]
}
],
"suggestText" : [
{
"text" : "qiy",
"offset" : 0,
"length" : 3,
"options" : [ ]
}
]
}
}  

 输入【qi业】的查询结果为

{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"full_pinyin" : [
{
"text" : "qi业",
"offset" : 0,
"length" : 3,
"options" : [
{
"text" : "企业规划",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "9TgnlHMBSEyTxFiDO4lU",
"_score" : 1.0,
"_source" : {
"kwsuggest" : "企业规划"
}
}
]
}
],
"like_pinyin" : [
{
"text" : "qi业",
"offset" : 0,
"length" : 3,
"options" : [
{
"text" : "企业规划",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "9TgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "企业规划"
}
},
{
"text" : "祈愿设计 这是啥呢",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "9jgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "祈愿设计 这是啥呢"
}
},
{
"text" : "起夜的风光 真的美",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "-jgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜的风光 真的美"
}
},
{
"text" : "起夜第二个词 测试使用",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "aDg3lHMBSEyTxFiDXprV",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜第二个词 测试使用"
}
}
]
}
],
"prefix_pinyin" : [
{
"text" : "qi业",
"offset" : 0,
"length" : 3,
"options" : [ ]
}
],
"suggestText" : [
{
"text" : "qi业",
"offset" : 0,
"length" : 3,
"options" : [ ]
}
]
}
}

  输入【qy】的结果为

{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"suggest" : {
"full_pinyin" : [
{
"text" : "qy",
"offset" : 0,
"length" : 2,
"options" : [ ]
}
],
"like_pinyin" : [
{
"text" : "qy",
"offset" : 0,
"length" : 2,
"options" : [
{
"text" : "起夜的风光 真的美",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "-jgnlHMBSEyTxFiDO4lU",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜的风光 真的美"
}
},
{
"text" : "起夜第二个词 测试使用",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "aDg3lHMBSEyTxFiDXprV",
"_score" : 2.0,
"_source" : {
"kwsuggest" : "起夜第二个词 测试使用"
}
}
]
}
],
"prefix_pinyin" : [
{
"text" : "qy",
"offset" : 0,
"length" : 2,
"options" : [
{
"text" : "起夜的风光 真的美",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "-jgnlHMBSEyTxFiDO4lU",
"_score" : 1.0,
"_source" : {
"kwsuggest" : "起夜的风光 真的美"
}
},
{
"text" : "起夜第二个词 测试使用",
"_index" : "suggest_tset",
"_type" : "_doc",
"_id" : "aDg3lHMBSEyTxFiDXprV",
"_score" : 1.0,
"_source" : {
"kwsuggest" : "起夜第二个词 测试使用"
}
}
]
}
],
"suggestText" : [
{
"text" : "qy",
"offset" : 0,
"length" : 2,
"options" : [ ]
}
]
}
}

  

5.java版本代码

这里使用elasticsearch-rest-high-level-client

application.yml添加配置

# ES配置
elasticsearch:
ipAddress: [127.0.0.1:9200]

添加配置类

@Component
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
@Data
public class ElasticsearchRestClientConfig {
private Logger logger = LoggerFactory.getLogger(getClass()); private static final int ADDRESS_LENGTH = 2;
private static final String HTTP_SCHEME = "http"; /**
* 使用冒号隔开ip和端口
*/
public String[] ipAddress; @Bean
public RestClientBuilder restClientBuilder() {
HttpHost[] hosts = Arrays.stream(ipAddress)
.map(this::makeHttpHost)
.filter(Objects::nonNull)
.toArray(HttpHost[]::new);
logger.debug("hosts:{}", Arrays.toString(hosts));
return RestClient.builder(hosts);
} @Bean(name = "highLevelClient")
public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
return new RestHighLevelClient(restClientBuilder);
} private HttpHost makeHttpHost(String s) {
assert StringUtils.isNotEmpty(s);
String[] address = s.split(":");
if (address.length == ADDRESS_LENGTH) {
String ip = address[0];
int port = Integer.parseInt(address[1]);
return new HttpHost(ip, port, HTTP_SCHEME);
} else {
return null;
}
}
}

实现的代码:

@Service
public class KwSuggestService implements IKwSuggest {
@Autowired
RestHighLevelClient highLevelClient; @Override
public List<String> GetKwSuggestList(String kw){
SearchRequest searchRequest = new SearchRequest("suggest_tset");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
SuggestBuilder suggestBuilder=new SuggestBuilder();
suggestBuilder.addSuggestion("suggestText", SuggestBuilders.completionSuggestion("kwsuggest.suggestText").prefix(kw).skipDuplicates(true).size(5));
suggestBuilder.addSuggestion("full_pinyin", SuggestBuilders.completionSuggestion("kwsuggest.full_pinyin").prefix(kw).skipDuplicates(true).size(5));
suggestBuilder.addSuggestion("prefix_pinyin", SuggestBuilders.completionSuggestion("kwsuggest.prefix_pinyin").prefix(kw).skipDuplicates(true).size(5));
suggestBuilder.addSuggestion("like_pinyin", SuggestBuilders.completionSuggestion("kwsuggest.like_pinyin").prefix(kw, Fuzziness.fromEdits(1)).skipDuplicates(true).size(5));
sourceBuilder.suggest(suggestBuilder);
sourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
List<String> result = new ArrayList<>();
List<String> suggestionList= Arrays.asList("suggestText","full_pinyin","prefix_pinyin","like_pinyin");
try {
SearchResponse response = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Suggest suggestions = response.getSuggest();
Integer index = 1;
for(String suggestionType : suggestionList){
CompletionSuggestion completionSuggestion = suggestions.getSuggestion(suggestionType);
for (CompletionSuggestion.Entry entry : completionSuggestion.getEntries()) {
for (CompletionSuggestion.Entry.Option option : entry) {
String suggestText = option.getHit().getSourceAsMap().get("kwsuggest").toString();
result.add(suggestText);
}
}
// 按照中文匹配、全拼匹配、拼音首字母匹配、模糊匹配的顺序,结果大于5的时候返回结果,根据自己业务需要判断这个返回的数量
if(result.size()>=5){
break;
}
// 中文匹配,全拼匹配以及拼音首字母匹配存在结果的,不需要模糊匹配
if(index==3 && result.size()>0){
break;
}
// 超过3个字模糊匹配不准确
if(kw.length()>3 && result.size()==0){
break;
}
}
return result;
} catch (IOException e) {
e.printStackTrace();
return new ArrayList<>();
}
}
}

  

6..c#代码实现

C#使用的是NEST

 public partial class ElasticFactory
{
public ExternalServiceResponse<KeywordsSuggestResponseDataEntity> GetKeywordsSuggest(ElasticKeywordsSuggestRequest request)
{
var result = new ExternalServiceResponse<KeywordsSuggestResponseDataEntity>(); try
{
if (string.IsNullOrEmpty(request.q)) return result; var nodes = new Uri[0];
nodes[0] = new Uri("http://127.0.0.1:9200");
var pool = new StaticConnectionPool(nodes);
var settings = new ConnectionSettings(pool).DefaultIndex("suggest_tset");
var client = new ElasticClient(settings); string[] keys = new[] { "suggestText", "full_pinyin", "prefix_pinyin", "like_pinyin" };
SearchDescriptor<object> search = new SearchDescriptor<object>();
search
.Source(r => r
.Includes(f => f
.Fields("kw")
)
)
.Suggest(s => s.Completion(keys[0], c => c.Field("kwsuggest.suggestText").SkipDuplicates(true).Prefix(request.q).SkipDuplicates())
.Completion(keys[1], c => c.Field("kwsuggest.full_pinyin").SkipDuplicates(true).Prefix(request.q).SkipDuplicates())
.Completion(keys[2], c => c.Field("kwsuggest.prefix_pinyin").SkipDuplicates(true).Prefix(request.q).SkipDuplicates())
.Completion(keys[3], c => c.Field("kwsuggest.like_pinyin").SkipDuplicates(true).Prefix(request.q).SkipDuplicates().Fuzzy(m=>m.Fuzziness(Fuzziness.EditDistance(1)))))
;
var esResult = client.Search<dynamic>(s => search);
if (esResult != null)
{
result.code = 1;
result.data = new KeywordsSuggestResponseDataEntity();
//1.先获取中文全匹配
//2.上面不满5个,再匹配全拼
//3.上面不满5个,中文全拼匹配首字母
//4.上面都没有用模糊匹配
if (esResult.Suggest != null)
{
result.data.items = new List<KeywordsSuggestResponseItemEntity>();
int index = 1;
foreach (var key in keys)
{
AddSuggestItems(esResult.Suggest, key, result.data.items);
//1-3之间,够了5个就返回
if (index >= 1 && index <= 3 && result.data.items.Count >= 5)
{
result.data.items = result.data.items.Skip(0).Take(5).ToList();
break;
}
//到了第3步如果还没有满足5个,直接返回,模糊匹配不精确
if (index == 3 && result.data.items.Count > 0)
{
break;
}
//输入的字符数大于3个以上,前面没有关键词匹配,后面不做模糊处理,匹配度太差了
if (index == 3 && request.q.Length>3)
{
break;
}
index++;
}
result.data.num = result.data.items.Count;
}
else
{
result.data.num = 0;
}
}
else
{
result.code = 0;
result.msg = "查询失败";
}
}
catch (Exception ex)
{
result.code = 0;
result.msg = ex.Message;
} return result;
} private void AddSuggestItems(ISuggestDictionary<dynamic> suggest, string key, List<KeywordsSuggestResponseItemEntity> items)
{
var suggestFullPinyin = suggest[key];
if (suggestFullPinyin != null)
{
foreach (var hit in suggestFullPinyin[0].Options)
{
string kwSource = hit.Source["kwsuggest"];
//已经存在的不要重复添加
if (items.Any(m => m.kw == kwSource))
{
continue;
}
items.Add(new KeywordsSuggestResponseItemEntity() { kw = kwSource });
}
}
}
}

  

Elasticsearch实现搜索推荐词的更多相关文章

  1. Elasticsearch分布式搜索和数据分析引擎-ElasticStack(上)v7.14.0

    Elasticsearch概述 **本人博客网站 **IT小神 www.itxiaoshen.com Elasticsearch官网地址 https://www.elastic.co/cn/elast ...

  2. 23.match_phrase_prefix实现search-time搜索推荐

    主要知识点: 搜索推荐的使用场景 用法 原理 一.搜索推荐的使用场景 搜索推荐,就是在你做搜索时,当你写出一部搜索词时,es会自提示接下来要写的词,比如当你在搜索hello w 时,如果es中有如下文 ...

  3. elasticsearch对无意义的词进行屏蔽——停用词

    介绍 在使用elasticsearch进行搜索业务的时候,发现一篇和搜索关键字完全不匹配的文章排在最前面.打开它发现原来是这篇文章含有非常多的"的"这个无意义的词.而我的搜索关键字 ...

  4. 十九种Elasticsearch字符串搜索方式终极介绍

    前言 刚开始接触Elasticsearch的时候被Elasticsearch的搜索功能搞得晕头转向,每次想在Kibana里面查询某个字段的时候,查出来的结果经常不是自己想要的,然而又不知道问题出在了哪 ...

  5. Elasticsearch 为了搜索

    前言 Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene 基础之上. Lucene 可以说是当下最先进.高性能.全功能的搜索引擎库--无论是开源还是 ...

  6. Elasticsearch之停用词

    前提 什么是倒排索引? Elasticsearch之分词器的作用 Elasticsearch之分词器的工作流程 Elasticsearch的停用词 1.有些词在文本中出现的频率非常高,但是对文本所携带 ...

  7. ElasticSearch位置搜索

    ElasticSearch位置搜索 学习了:https://blog.csdn.net/bingduanlbd/article/details/52253542 学习了:https://blog.cs ...

  8. ElasticSearch入门-搜索(java api)

    ElasticSearch入门-搜索(java api) package com.qlyd.searchhelper; import java.util.Map; import net.sf.json ...

  9. 24.通过ngram分词机制实现index-time搜索推荐

    一.ngram和index-time搜索推荐原理     1.什么是ngram     假设有一个单词:quick,在5种长度下的ngram情况如下: ngram length=1,q u i c k ...

随机推荐

  1. Mac 搭建 Redis 集群

    date: 2020-09-24 16:24:00 updated: 2020-09-24 17:30:00 Mac 搭建 Redis 集群 参考文档 摘要 安装docker brew cask in ...

  2. 如何发布代码到maven中心仓库

    deploy to sonatype 参考文章 https://blog.csdn.net/xuefu_78/article/details/52494698 https://blog.csdn.ne ...

  3. C2. Pokémon Army (hard version) 解析(思維)

    Codeforce 1420 C2. Pokémon Army (hard version) 解析(思維) 今天我們來看看CF1420C2 題目連結 題目 略,請直接看原題. 前言 根本想不到這個等價 ...

  4. vue学习笔记(六) ----- vue组件

    一.模块化与组件化 模块化的定义 模块化在Node中是一个规范,定义一些模块的相关的规则,从代码角度上来说,方便做区别,如果不使用模块化,写在js文件中不利于后期维护和扩展,从代码的层面上就把相关的功 ...

  5. Software Construction内容归纳

    本篇博文是对于2020春季学期<软件构造>课程的总结归纳,由于原先编辑于word,格式不方便直接导入该博客,可以到本人github中进行自取. https://github.com/zqy ...

  6. java查询elasticsearch聚合

    java查es多分组聚合: SearchRequestBuilder requestBuilderOfLastMonth = transportClient.prepareSearch(TYPE_NA ...

  7. Flask中的MTV架构之Templates

    Flask 中的MTV架构之Templates 关注公众号"轻松学编程"了解更多. 1.Templates(模板引擎) 1.1 说明 ​ 模板文件就是按照特定规则书写的一个负责展示 ...

  8. 解决SBT下载慢,dump project structure from sbt?

    一. 安装SBT,参考https://blog.csdn.net/zcf1002797280/article/details/49677881 二. 在~/.sbt下新建repositories添加如 ...

  9. 错误解析:org.apache.catalina.LifecycleException: Protocol handler start failed

    以下是报错代码: org.apache.catalina.LifecycleException: Protocol handler start failed at org.apache.catalin ...

  10. Luogu P1856 [USACO5.5]矩形周长Picture

    线段树+扫描线 经典的扫描线问题 首先将一个矩形看作由竖着的两条边和横着的两条边构成 那分成两次考虑,一次考虑竖边,一次考虑横边 首先考虑横边 如图两个矩形,现将横边擦去,留下竖边,将平面划分成3个区 ...