因为 shared_ptr 有两个数据乘员,读写操作不能原子化,使得多线程读写同一个 shared_ptr 对象时需要加锁。
当执行 shared_ptr y = x 时涉及两个成员的复制,这两步拷贝不会同时发生。
unordered_map的底层实现是hashtable,采⽤开链法(也就是⽤桶)来解决哈希冲突,当桶的⼤⼩超
过8时,就⾃动转为红⿊树进⾏组织。
auto 让编译器通过初始值来进行类型的推算。从而获得定义变量的类型,所以说auto定义的变量必须有初始值。
decltype可以从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。
友元函数是定义在类外的普通函数,不属于任何类,可以访问其他类的私有成员。但是需要在类的定义中声明所有可以访问它的友元函数。
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
但是另一个类里面也要相应的进行声明。
(1)友元关系不能被继承。(2)友元关系是单向的。(3)友元关系不具有传递性。
变量:被static修饰的变量是静态变量,会在程序运行的过程中一直存在,放在静态存储区。局部静态变量作用域在函数体中;全局静态变量作用域在文件里。
函数:被static修饰的函数就是静态函数,静态函数只能在本文件中使用,不能被其他文件调用,也不会和其他文件中的同名函数冲突。
类:在类中,被static修饰的成员变量是类静态成员,会被类的多个对象共用,不会属于某个对象,只能在类外初始化。访问这个静态函数通过引用类名来访问。类静态成员函数只能访问类静态成员变量。
C++ 接口是使用抽象类来实现的,如果类至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
设计抽象类的目的,是为了给其他类提供一个可以继承的适当基类。抽象类不能用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类对象,会导致编译错误。
因此,如果一个子类需要被实例化,则必须实现每个虚函数,这也意味着c++支持使用抽象类声明的接口。如果没有在派生类中重写虚函数,就尝试实例化该类对象,会导致编译错误。
class Box
{
public:
// 纯虚函数
virtual double getVolume() = 0;
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
静态类型:对象在声明时采用的类型,叫做静态类型。
动态类型:通常指一个指针或者引用目前所指向的对象类型,是在运行期间决定的。
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表必须不同。函数重载常用来处理实现功能类似,而数据类型不同的问题。
(函数汇总出来的符号不同)
在C语言中,汇编阶段对符号进行汇总时,一个函数汇总后的符号就是其函数名,所以当汇总时发现多个相同的函数符号时,编译器会报错。
在C++中,在对符号进行汇总时,对函数的名字修饰做了改动,函数汇总出的符号不单单只有函数名,还有其参数的类型和个数以及顺序等等。所以就算是同名函数,汇总出来的符号也不同。
野指针指的是指向了一个不可用的内存地址,往往会造成内存越界、段错误等问题。
产生的原因:
如何避免野指针?
dynamic_cast:用于动态类型转换。具体的说,就是基类指针到派生类指针,或者派生类到基类指针的转换。提供运行时类型检查,只用于Base包含至少一个虚函数的类。
static_cast:用于各种隐式转换。具体的说,就是用户基本类型之间的转换。比如将int换成char,float换成int等。不提供运行时类型的检查,会有安全隐患。
const_cast:const_cast 可以用来设置或者一处指针所指向对象的 const。
reinterpret_cast:能够完成任意指针类型的转换。该转换操作的结果是出现一份完全相同的二进制复制品,既不会有指向内容的检查,也不会有指针本身类型的检查。
什么是内存对齐?
struct{
int x;
char y;
}s;
理论下,32位系统下,int 占 4byte,char 占1 byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐导致的。
为什么要进行内存对齐?
因为大部分处理器在存取内存时,以双字节,4字节,8字节,16字节甚至32字节为单位来存取内存的。如果不进行内存对齐,需要剔除不要的数据。
内存对齐的规则
每个平台的编译器都有自己默认的 ”对齐系数“,可以通过预编译命令 #pragma pack(n) 来改变这一系数。
有效对齐值= min( #pragma pack(n) , 结构体中最长数据类型长度 )
对于结构体中的每个成员,对齐时需要遵循的规则:
(1) 每个成员的首地址都是 min(该成员大小, 有效对齐数) 的整数倍。
(2) 结构体的总大小为有效对齐数的整数倍。
了解规则后,定义结构体时需要考虑成员变量定义的先后顺序:

浅拷贝就是对象的数据成员之间的简单赋值。但是当拷贝对象中有对其他资源(如堆、文件、系统)的引用时,对象会另开辟一块新的资源,而不再对拷贝对象中引用其他资源的对象的指针或引用进行单纯的赋值。避免两个对象析构时,释放同一块内存。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否在是浅拷贝。
深拷贝要自定义拷贝构造函数的内容。
参考资料:
https://blog.csdn.net/u010700335/article/details/39830425
左值引用:左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用。但使用常引用后,我们只能通过引用去读取数据,无法去修改数据。
参考资料:
https://zhuanlan.zhihu.com/p/97128024
在C++中,内存分成5个区,从高地址到底地址分别是 栈、堆、全局/静态存储区、常量存储区和代码区。
动态分配的内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。内存泄漏的几种原因:
静态链接:链接阶段时,链接器从静态库文件中复制所需的代码到可生成执行文件中。优点是编译后执行文件不需要外部库的支持。缺点是当静态函数库改变后,需要重新编译。
动态链接:当程序执行到相关函数的时候才调用库中的函数。优点是多个应用程序在使用同一个库时非常适合。缺点是程序的运行环境中必须有这个库。
不管是静态库还是动态库,都是由.o目标文件生成的。
make 在编译期的类型检查阶段,会根据参数类型的不同,将OMAKE节点转换成 OMAKESLICE、OMAKECHAN、OMAKEMAP 三种类型,这些节点最终会调用不同的运行时函数来进行初始化数据结构。

new
200 OK 请求成功
301 Move Permanently请求的资源已被永久的移动到新URL
400 Bad Request 客户端请求的语法错误
403 Forbidden 禁止访问
404 Not Found 请求的资源不存在
三次握手:
四次挥手:
两次握手:


TCP是面向字节流的协议,UDP是面向报文的协议。
每个UDP报文就是一个用户消息的边界。
当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文,也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输。
TCP粘包问题解决:
按数据结构分类:B+Tree索引、Hash索引。
按物理存储分类:聚簇索引(主键索引)、二级索引(辅助索引)。
按字段特性分类:主键索引、唯一索引、普通索引、前缀索引。
按字段个数分类:单列索引、联合索引。
所以,在查询时使用了二级索引,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表。
做 mysql 优化时,我们用 explain 来查看 sql 的执行计划。可以查看到:
索引下推(Index Condition Pushdown,简称ICP):指将部分上层(服务层)负责的事情,交给了下层(引擎层)去处理。没有 ICP 的情况,把查询的所有结果回表,回表后的查询结果在服务层再进行过滤;使用 ICP 后,先按照索引的部分信息进行过滤,然后再把过滤后的数据进行回表,最后交给服务层再过滤。
数据类型:
char、varchar、int、float、date、datetime、timestamp
char 与 varchar 的区别:(常见面试题)
date、datetime、timestamp 的区别:
约束:
NOT NULL:保证该字段值一定不为空;
DEFAULT:保证字段有默认值;
PRIMARY KEY:标志一列或者多咧,并保证其值在表内的唯一性。
UNIQUE:限制一列或多列的值,保证字段值在表内的唯一性。
参考资料:
char 与 varchar 的区别:https://leetcode.cn/leetbook/read/database-handbook/pxcw1g/
SQL 约束有哪几种类型:https://leetcode.cn/leetbook/read/database-handbook/pxy7ce/
B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;
B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。
MyISAM,B+Tree叶节点的data域存放的是数据记录的地址。辅助索引(Secondary key)和主索引在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。
InnoDB,主键索引中B+树的节点的data域保存的是完整的数据。辅助索引都引用主键作为data域。
事务:InnoDB支持事务;MyISAM不支持事务。
并发:InnoDB支持行级锁;MyISAM只支持表级锁。
外键:InnoDB支持外键;MyISAM不支持。
查询速度:MyISAM 比 InnoDB 快。
InnoDB 与 MyISAM 的差别
Mysql除了普通查询是快照读,其他查询都是当前读(包括下面两个语句):
select…lock in share mode (共享读锁)
select…for update
快照读通过MVCC来解决幻读。
当前读通过next-key lock(记录锁+间隙锁)的方式来解决幻读
悲观锁:先获取锁,再进行业务操作,类似于 SELECT…FOR UPDATE 这样的语句,对数据加锁,避免其他事务意外修改数据。
乐观锁:先进行业务处理,只在最后更新数据时进行检查数据是否被更新过。适用于 读多写少 的应用场景。乐观锁有三种常用的实现形式:
分库:当QPS过高,数据库连接数不足时,把各个业务的数据从一个单一的数据库中拆分开,分别放入到单独的数据库中。
分表:当单表数据量非常大时,此时的QPS不高,数据库的连接数还够时,可以通过将数据拆分到多个表中,来减少单表的数据量。一般我们认为当单表行数超过500万条或者单表容量超过2G时,才需要做分库分表。
五种常见的数据结构:
String:SDS(好处:拼接字符串不会导致缓冲区溢出,空间不够会自动扩容;获取长度的时间复杂度时O(1);可以保存二进制文件)
Hash:哈希表、压缩列表
List:双向链表、压缩列表
Set:整数集合、哈希表
Sorted Set(ZSet):跳表、压缩列表
Redis 使用的过期删除策略是「惰性删除+定期删除」。
Redis 内存数据量达到一定限制的时候,就会实行数据淘汰策略(回收策略)。Redis 会根据 maxmemory-policy 配置策略,来决定具体的行为。Redis 的内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。
参考资料:
https://leetcode.cn/leetbook/read/database-handbook/p5pwd5/
https://www.xiaolincoding.com/redis/base/redis_interview.html#redis-%E8%BF%87%E6%9C%9F%E5%88%A0%E9%99%A4%E4%B8%8E%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0
布隆过滤器:k个哈希函数计算出k个散列值,之后取模描黑(数组中对应的比特位置为1),判断时还是k个哈希函数取模做判断,只有全为黑(全是1)才属于,有一个为白就不属于。所以有可能把白判断为黑,但绝不可能把黑判断为白。
三个公式:
n = 样本量;
p = 失误率;
m = 最优的位数组大小;
k = Hash 函数个数选取最优数目;


算法错误的认为某一原本不在集合中的元素却被检测为在该集合中(False Positives),该概率(失误率p)由以下公式确定:

使用:
有两种方式:使用Redis4.0版本提供的插件功能、使用bitmap(位图)来实现布隆过滤器。
https://blog.csdn.net/qq_38571892/article/details/123503418
目前,Redis 的持久化主要有两大机制,即 AOF(Append Only File)日志和 RDB(Redis Database)快照。
AOF 日志:
AOF日志是写后日志,即先执行命令,再记日志。AOF 是以文件的形式在记录接收到的所有写命令。AOF日志有三种写回磁盘的策略(配置项 appendfsync),分别是 always、everysec、no。随着接收的写命令越来越多,AOF 文件会越来越大,为了避免日志文件过大,Redis 提供了 AOF 重写机制。
AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取,它针对的是数据库中键的当前值。和 AOF 日志由主线程写回不同,重写过程是由后台子线程 bgrewriteaof 来完成的。重写的过程总结为“一个拷贝,两处日志”。
“一个拷贝”:主线程 fork 出后台的 bgrewriteaof 子进程,执行一次内存拷贝,用于重写。
“两处日志”:子进程在进行AOF重写期间,主进程还需要继续处理命令,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。此时,如果有写操作,第一处日志是指 Redis 会把这个操作追加写到「AOF缓冲区」,保证现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。而第二处日志,是指 Redis 会把这个操作写到「AOF重写缓冲区」中。等到拷贝数据的所有操作记录重写完成后,再将「AOF重写缓冲区」中的所有内容追加写入到新的 AOF 文件,并覆盖原有的文件。

RDB 快照:
用 AOF 日志的方式来恢复数据其实是很慢的,因为 Redis 执行命令由单线程负责,而 AOF 日志恢复数据的方式是顺序执行日志里的每一条数据。于是有另一种持久化方法:内存快照。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录。而这个快照文件就称为 RDB文件。
Redis 的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照。Redis 提供来两个命令来生成 RDB 文件,分别是 save 和 bgsave。
为了执行快照的同时,正常处理写操作。Redis 借助操作系统提供的写时复制技术(Copy-On-Write,COW)。当主线程要修改一块数据的时候,这块数据会被复制一份,生成该数据的副本。然后主线程在这个数据副本上进行修改。同时,子进程可以继续把原来的数据写入 RDB 文件中。此时会出现以下问题:
在做了一次全量快照后,后续的快照只对修改的数据进行快照记录,称为增量快照。
Redis 4.0 中提出了一个混合使用AOF日志和内存快照的方法。内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
参考资料:
https://www.xiaolincoding.com/redis/storage/aof.html#%E6%80%BB%E7%BB%93
https://time.geekbang.org/column/article/271839
https://redisbook.readthedocs.io/en/latest/internal/aof.html
**强一致性:**系统写入什么,读出来的也会是什么,用户体验好,实现起来往往对系统的影响大。
**弱一致性:**这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能保证到某个时间级别后,数据能够达到一致。
**最终一致性:**最终一致性是弱一致性的特例,系统会保证在一定时间内,能够达到一个数据一致的状态。
Cache-Aside:旁路缓存模式。
Read-Through/Write-Through:读写穿透。
Read/Write Through模式中,应用程序跟数据库缓存交互,都是通过抽象缓存层实现的。
Write behind:异步缓存写入。
Read/Write Through 是同步更新缓存和数据,Write Behind 则是只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。缺点:缓存和数据库的一致性不强,对一致性要求高的系统要谨慎使用。
IO的两个阶段:
数据准备阶段。内核从设备读取数据到内核空间的缓冲区。(内核从IO设备读数据)
内核空间复制回用户空间进程缓冲区阶段。(进程从内核复制数据)
epoll 全称 eventpoll,是 Linux 进行IO多路复用的一种方式,用于在一个线程里监听多个IO源,在IO源可用的时候返回并进行操作。它的特点是基于事件驱动,性能很高。
epoll 将文件描述符拷贝到内核空间后使用红黑树来进行维护,同时向内核注册每个文件描述符的回调函数。当某个文件描述符可读可写的时候,将这个文件描述符加入到就绪的链表中,并唤起进程,返回就绪链表到用户空间,由用户程序进行处理。
epoll 有三个系统调用:epoll_create(), epoll_ctl() 和 epoll_wait()
epoll_create() 函数在内核中初始化一个epoll对象,同时初始化红黑树和就绪链表。
epoll_ctl() 用来对监听的文件描述符进行管理。将文件描述符插入到红黑树,或者从红黑树中删除,这个过程的时间复杂度是log(N)。同时向内核注册文件描述符的回调函数。
epoll_wait() 会将进程放到epoll的等待队列中,将进程阻塞,当某个文件描述符的IO可用时,内核通过回调函数将该文件描述符放到就绪链表里,epoll_wait() 会将就绪链表里的文件描述符返回到用户空间。
参考资料:
https://zhuanlan.zhihu.com/p/56486633
参考资料:
https://www.xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#select-poll
适用场景:Kafka更适合日志处理,RocketMQ更适合业务的处理。
性能:Kafka吞吐量更高,单机百万/秒,RocketMQ单机10万/秒。
可靠性:Kafka使用异步刷盘方式,异步 Replication;RocketMQ 支持异步/同步刷盘;异步/同步 Replication。
消费失败重试:Kafka不支持消费失败重试,RocketMQ消费失败支持定时重试,每次重试间隔时间顺延。
定时/延时消息:Kafka不支持定时消息,RocketMQ支持定时消息。
分布式事务(附):Kafka在提交事务失败的时候,直接抛出异常,让用户进行重试。RocketMQ有事务反查的机制,定期反查事务状态,来补偿提交事务消息可能出现的通讯问题。

在消息传递过程中,如果出现传递失败的情况,发送方会执行重试,重试的过程中就有可能会产生重复的消息。一般解决重复消息的办法是,在消费端,让我们消费消息的操作具有幂等性。所以,常用的设计幂等操作的方法:
参考资料:
如何处理消费过程中的重复消息:https://time.geekbang.org/column/article/111552
扩容 Consumer 的实例数量与主题中分区的数量。
分页:产生内部碎片。
分段:按照逻辑模块来划分内存,段式管理容易产生外部碎片。
段页式管理:现将用户程序分成若干个段,再把每个段分成若干页。

先找段表始址,再去段表通过段号找到页表始址,再通过页号找到内存块号,再通过页内偏移量计算实际的物理地址。
用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态执行。内核空间中的代码可以访问所有内存,我们称这些程序在内核态执行。
用户态和内核态是操作系统的两种运行级别,两者最大的区别就是特权级不同。
参考资料:
https://segmentfault.com/a/1190000039774784
线程同步:线程同步是指线程之间所具有的一种制约关系,例如:两个线程A和B在运行过程中协同步调,按预定的先后次序运行,比如 A 任务的运行依赖于 B 任务产生的数据。
线程互斥:线程互斥是指对于共享的操作系统资源,在各线程访问时具有排它性。如果A占有了该资源则B需要等待A释放。
进程切换分两步:
进程切换涉及到虚拟地址空间的切换,导致TLB失效,导致虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会。
每个线程都有独立一套的寄存器和栈,当同一个进程发生线程切换的时候,只需要切换线程的私有数据、寄存器等不共享的数据。
管道:

在 shell 里面执行A|B 命令的时候,A进程和B进程都是shell创建出来的子进程,A和B之间不存在父子关系,它俩的父进程都是shell。
对于匿名管道,它的通信范围是存在父子关系的进程。
共享内存:共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,不需要拷贝来拷贝去。

信号量:主要用PV操作来实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
信号:对于异常情况下的工作模式,就需要用“信号”的方式来通知进程。
Socket:跨网络与不同主机上的进程之间通信,就需要Socket。
在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。
;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wwau2Vq-1665651010818)(https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost2/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E9%9B%B6%E6%8B%B7%E8%B4%9D/senfile-%E9%9B%B6%E6%8B%B7%E8%B4%9D.png)]
参考资料:
https://www.xiaolincoding.com/os/8_network_system/zero_copy.html#sendfile
CI(CI-Continuous integration,持续集成)
持续集成是指多名开发者在开发不同功能代码的过程当中,可以频繁地将代码行合并到一起并且相互不影响工作。
持续集成的目的,是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,**代码集成到主干之前,必须通过自动化测试(pipeline)。**只要有一个测试用例失败,就不能集成。
方法一:假设三角形三个点为x,y,z,随机 a , b ∈ [ 0 , 1 ] a, b \in [0,1] a,b∈[0,1],条件为 a + b ∈ [ 0 , 1 ] a + b \in [0,1] a+b∈[0,1],向量 a X Y ⃗ + b X Z ⃗ a\vec{XY} + b\vec{XZ} aXY+bXZ就是答案,问题就变成了在正方形里随机点,使得点在下三角中。如果点在上三角中,则翻折三角形。
方法二:三个随机数 a,b,c 满足 a+b+c = 1,然后画出点 ax+by+cz 即可。
x = np.array([0.0, 0.0])
y = np.array([2.0, 0.0])
z = np.array([1.0, math.sqrt(3)])
plt.plot([0.0, 2.0], [0.0, 0.0], color='black')
plt.plot([0.0, 1.0], [0.0, math.sqrt(3)], color='black')
plt.plot([1.0, 2.0], [math.sqrt(3), 0.0], color='black')
for i in range(1000):
a = random.uniform(0,1)
b = random.uniform(0,1)
if a + b > 1: # 关于正方形副对角线对称
d = (a + b - 1) / 2
a -= 2 * d
b -= 2 * d
va = y - x
vb = z - x
point = va * a + vb * b
print(a, b, point)
plt.scatter(point[0], point[1])
plt.show()
x = np.array([0.0, 0.0])
y = np.array([2.0, 0.0])
z = np.array([1.0, math.sqrt(3)])
plt.plot([0.0, 2.0], [0.0, 0.0], color='black')
plt.plot([0.0, 1.0], [0.0, math.sqrt(3)], color='black')
plt.plot([1.0, 2.0], [math.sqrt(3), 0.0], color='black')
for i in range(1000):
a = random.uniform(0,1)
b = random.uniform(0,1)
if a > b:
a = a + b
b = a - b
a = a - b
point = a * x + (b - a) * y + (1 - b) * z
print(a, b, point)
plt.scatter(point[0], point[1])
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrCKf4LM-1665651010819)(./myplot)]
只需要 计算点与多边形顶点的面积和 是否等于 多边形的面积 即可。
大根堆是一个完全二叉树。
插入的时候会插入到完全二叉树的最右边,然后进行上浮。
删除的时候先把堆顶与最右边的节点交换,之后将最右边的节点删掉。最后将新换上来的节点下沉。

统计相交回文串的个数
struct PAM {
// num[i] 以 i 节点为后缀的回文字符串所包含的回文串数量。
// cnt[i] 整串中能形成 i 节点回文串的数量。
int fail[N], step[N], g[N][27], n, last, tot, cnt[N], num[N];
void init() {
memset(step,0,sizeof(step));
memset(fail,0,sizeof(fail));
memset(num,0,sizeof(num));
memset(cnt,0,sizeof(cnt));
memset(g,0,sizeof(g));
fail[0] = fail[1] = 1; step[1] = -1; last = 0; tot = 1; n = -1;
}
int insert(int nx) {
int p = last; n++;
while(s[n] != s[n - step[p] - 1]) p = fail[p];
if(!g[p][nx]) {
int np = ++tot; step[np] = step[p] + 2;
int q = fail[p];
while(s[n] != s[n - step[q] - 1]) q = fail[q];
fail[np] = g[q][nx]; g[p][nx] = np;
num[np] = num[fail[np]] + 1;
}last = g[p][nx];
cnt[last] ++;
return num[last];
}
int calc() { // 计算回文数的个数
int ret = 0;
for(int i=tot;i>=1;i--) {
cnt[fail[i]] = (cnt[fail[i]] + cnt[i]) % mod;
ret = (ret + cnt[i]) % mod;
}
return ret;
}
}pam;
给定母串,统计母串中含子串或其循环同构串的个数。
后缀自动机的原理:
struct SAM {
// cnt[i] 表示当前节点代表了多少个子串
int fail[N], step[N], g[N][27], last, tot, R[N], r[N];
long long cnt[N];
bool vis[N];
void init() {
memset(fail,0,sizeof(fail));
memset(step,0,sizeof(step));
memset(g,0,sizeof(g));
memset(cnt,0,sizeof(cnt));
memset(R,0,sizeof(R));
memset(r,0,sizeof(r));
memset(vis,0,sizeof(vis));
last = tot = 1;
}
void insert(int nx) {
int p = last; int np = ++tot; step[np] = step[p] + 1;
while(p && !g[p][nx]) g[p][nx] = np, p = fail[p];
if(!p) fail[np] = 1;
else {
int q = g[p][nx];
if(step[q] == step[p] + 1) fail[np] = q;
else {
int nq = ++tot; step[nq] = step[p] + 1;
fail[nq] = fail[q]; fail[np] = fail[q] = nq;
for(int i=1;i<=26;i++) g[nq][i] = g[q][i];
while(p && g[p][nx] == q) g[p][nx] = nq, p = fail[p];
}
}last = np;
cnt[np] = 1;
}
void Rsort() {
for(int i=1;i<=tot;i++) R[step[i]] ++;
for(int i=1;i<=tot;i++) R[i] += R[i-1];
for(int i=tot;i>=1;i--) r[R[step[i]]--] = i;
for(int i=tot;i>=1;i--) cnt[fail[r[i]]] += cnt[r[i]];
}
}sam;