新普金娱乐网址


[引]VS2005扶植文书档案 : 加密 概述

老母的酿鱼饼

数学落到实处高质量纠删码引擎 | 纠删码技术详解(下)

  • 四月 13, 2019
  • 数学
  • 没有评论

直白只会差分的自笔者起来学习差分约束了!资瓷资瓷!

小编介绍: 

何以是差分约束呢?它意在消除不等式组求最值的题材。

徐祥曦,柒牛云工程师,独立开发了多套高品质纠删码/再生码编码引擎。
柳青(JeanLiu),华中国中国科学技术大学学技大学博士,研商方向为基于纠删码的分布式存款和储蓄系统。

比如说:HZOI2016
奖学金

WC模拟
奶牛排队

前言:

它把难点转化成图论_最短路模型来消除,把数学难题转化在图上,利用BF算法或SPFA。

在上篇《怎么着接纳纠删码编码引擎》中,我们简要精通了 Reed-SolomonCodes(中华VS
码)的编/解码进程,以及编码引擎的评议标准。但并不曾就现实贯彻进行拓展,本篇作为《纠删码技术详解》的下篇,大家将第一探索工程落到实处的题材。

留神到SPFA的松懈条件:

此处先不难提炼一下兑现高质量纠删码引擎的要点:首先,依据编码理论将矩阵以及有限域的演算工程化,接下去主要通过
SIMD 指令集以及缓存优化学工业作来展开加快运算。约等于说,大家得以将 昂CoraS
的工程完毕划分成四个着力步骤:

x ->(Eval) y : if(
far[x]+Eval<far[y] ) {… …}

  1. 将数学理论工程化

  2. 越来越的工程优化

正是说跑完以往总有
far[x]+Eval>=far[y] ;

那供给相关研究开发工程师对以下内容有所控制:

转换一下得到:far[x]-far[y]>=-Eval;

  1. 有限域的基本概念,蕴含有限域的转移与运算

  2. 矩阵的习性以及乘法规则

  3. 电脑类别布局中关于 CPU 指令以及缓存的争论

相当于满意四个二元3次不等式?

接下去,大家将根据那三个步骤并结成有关基础知识展开达成进程的演讲。

并且长得很像 x-y>=z 的事态?

1 、理论工程化

以 大切诺基S
码为例,纠删码完毕于现实的囤积系统能够分为几个部分:编码、解码和修复进度中的总结都以在有限域上开始展览的;编码进程就是总计生成矩阵(范德蒙德或柯西矩阵)和装有数据的乘积;解码则是持筹握算解码矩阵(生成矩阵中一些行向量组成的方阵的逆矩阵)和重建数据的乘积。

也等于说,假诺你列出了1雨后春笋不等式 {

壹.壹 有限域运算

有限域是纠删码中运算的基础域,全数的编解码和重建运算都以基有些有限域的。不止是纠删码,一般的编码方法都在有限域上拓展,比如大规模的AES加密中也有有限域运算。使用有限域的三个第一原由是电脑并无法确切执行Infiniti域的运算,比如合理数域和虚数域。

另外,在有限域上运算另1个至关心珍贵要的便宜是运算后的结果大小在听之任之范围内,那是因为有限域的封闭性决定的,那也为顺序设计提供了福利。比如在
路虎极光S 中,我们1般选取 GF(贰^8),即 0~25伍那一有限域,那是因为其尺寸刚好为1字节,便于大家对数码举行仓库储存和计量。

在明确了有限域的轻重之后,通过有限域上的变迁多项式可以找到该域上的生成元\[1\],进而通过生成元的幂次遍历有限域上的成分,利用那1质量我们能够转变对应的指数表。通过指数表大家得以求出对数表,再选择指数表与对数表最后生成乘法表。关于本原多项式的变通以及有关运算表的盘算能够参照小编在开源库中的数学工具。[2]

有了乘法表,大家就能够在运算进程中一向查表获得结果,而不用进行复杂的多项式运算了。同时也易于发现,查表优化将会变成接下去工作的重点与困难。

S[1]-S[2]<a;

一.二 选取生成矩阵

生成矩阵(GM, generator matrix)
定义了什么将原来数据块编码为冗余数据块,奇骏S
码的生成矩阵是八个 行 列矩阵,将 块原始数据块编码为 块冗余数据块。假设对应的编码是系统码(比如
RAID),编码后含有了原始数据,则生成矩阵中包罗二个 k×k 高低的单位矩阵和(nk)×k 的冗余矩阵,
单位矩阵对应的是固有数据块,冗余矩阵对应的是冗余数据块。非系统码未有单位矩阵,整个生成矩阵都以冗余矩阵,因而编码后唯有冗余数据块。平时我们会动用系统码以增长多少提取时的频率,那么接下去我们需求找到确切的冗余矩阵。

在解码进程中我们要对矩阵求逆,由此所利用的矩阵必须知足子矩阵可逆的性子。近年来产业界应用最多的二种矩阵是
Vandermonde matrix (范德蒙矩阵)和Cauchy matrix(柯西矩阵)。当中范德蒙矩阵历史最为悠久,但须求小心的是大家并不能够一向运用范德蒙矩阵

作为生成矩阵,而急需经过高斯消元后才能动用,那是因为在编码参数(k+m)相比大时会存在矩阵不可逆的高风险。

柯西矩阵运算容易,只可是供给计算乘法逆元,大家能够提前计算好乘法逆元表以供生成编码矩阵时使用。创制以柯西矩阵为生成矩阵的编码矩阵的伪代码如下图所示:

*// m 为编码矩阵*

*// rows为行数,cols为列数*

*// *k×k 的单位矩阵

**for **j := 0; j < cols; j++ {

   m[j][j] = byte(1)

}

*//* mxk 的柯西矩阵

**for **i := cols; i < rows; i++ {

   **for **j := 0; j < cols; j++ {

      d := i ^ j

      a := inverseTable[d]    *// 查乘法逆元表*

      m[i][j] = byte(a)

   }

}

S[2]-S[3]<=b;

 壹.三 矩阵求逆运算

有限域上的求逆方法和我们上学的线性代数中求逆方法同样,常见的是高斯消元法,算法复杂度是
O(n^三)。进程如下:

  1. 在待求逆的矩阵左边拼接三个单位矩阵
  2. 进行高斯消元运算
  3. 获取到的矩阵左侧非单位矩阵的一些作为求逆的结果,假诺不可逆则报错。

大家在事实上的测试环境中窥见,矩阵求逆的支付照旧比较大的(大致五千 ns/op)。思虑到在实际系统中,单盘数据重建往往须求多少个钟头可能更加长(磁盘I/O
占据绝大多数小时),求逆总括时间能够忽略不计。

S[3]-S[4]>=c;

二、进一步的工程优化

二.一 利用 SIMD 加快有限域运算

从上1篇作品可见,有限域上的乘法是透过查表获得的,每种字节和生成矩阵中元素的乘法结果通过查表得到,图一给出了按字节对原本数据举行编码的历程(生成多项式为x^八 + x^4 + x^三

  • x^2 + 1)。
    对于任意 1 字节来说,在 GF(2^8)
    内有25多种大概的值,所以未有成分对应的乘法表大小为 25陆字节。每一回查表能够进行2个字节数据的乘法运算,功用非常的低。

数学 1

 

图 一,
按字节对原始数据举办编码

当前主流的支撑 SIMD 相关指令的寄存器有 12八bit(XMM 指令)、256bit (YMM
指令)那二种体量,那代表对于六十七人的机器来说,分别提供了二到4倍的处理能力,大家可以设想使用 SIMD
指令并行地为越多数据开始展览乘法运算。

但各类成分的乘法表的大大小小为 25陆 Byte
,那大大高于了寄存器容纳能力。为了达到利用并行查表的目标,大家选择分治的商讨将五个字节的乘法运算进行拆分。

字节 y 与字节 a 的乘法运算进度可代表为,当中 y(a) 表示从 y
的乘法表中询问与 x 相乘结果的操作:

y(a) = y * a

 

大家将字节 a 拆分成高4个人(al) 与低 四 位 (ar) 四个部分,即(当中 ⊕

为异或运算):

a = (al << 4) ⊕ ar

 

这么字节 a 就代表为 0-一5 与 (0-1五 << 四)
异或运算的结果了。于是原先的 y 与 a 的乘法运算可代表为:

y(a) = y(al << 4) ⊕ y(ar)

 

鉴于 ar 与 al 的限定均为 0-壹5(0-111一),字节 y
与它们相乘的结果也就只有17个大概的值了 。那样原本25陆 字节的字节 y
的乘法表就能够被 2 张 1陆 字节的乘法表替换了。

上边以依照本原多项式 x^8 + x^4 + x^3 + x^2 + 1 转移的 GF(二^8)
为例,分别通过查询普通乘法表与运用拆分乘法表来演示 16 * 100
的测算进程。

1六 的完全乘法表为:

table = [0 16 32 48 64 80 96 112 128 144 160 176 192 208 224 240
 29 13 61 45 93 77 125 109 157 141 189 173 221 205 253 237 58 42 26 10 122 
106 90 74 186 170 154 138 250 234 218 202 39 55 7 23 103 119 71 87 167 183
 135 151 231 247 199 215 116 100 84 68 52 36 20 4 244 228 212 196 180 164 
148 132 105 121 73 89 41 57 9 25 233 249 201 217 169 185 137 153 78 94 110 
126 14 30 46 62 206 222 238 254 142 158 174 190 83 67 115 99 19 3 51 35 211
 195 243 227 147 131 179 163 232 248 200 216 168 184 136 152 104 120 72 88 
40 56 8 24 245 229 213 197 181 165 149 133 117 101 85 69 53 37 21 5 210 194 
242 226 146 130 178 162 82 66 114 98 18 2 50 34 207 223 239 255 143 159 175 
191 79 95 111 127 15 31 47 63 156 140 188 172 220 204 252 236 28 12 60 44 
92 76 124 108 129 145 161 177 193 209 225 241 1 17 33 49 65 81 97 113 166 
182 134 150 230 246 198 214 38 54 6 22 102 118 70 86 187 171 155 139 251 
235 219 203 59 43 27 11 123 107 91 75]

 

计算 16 * 100 可以直接查表得到:

table[100] = 14

 

16 的低二个人乘法表,也正是1六 与 0-一5 的乘法结果:

lowtable = [0 16 32 48 64 80 96 112 128 144 160 176 192 208 224 240]

 

1陆 的高3人乘法表,为1陆 与 0-1伍 << 四 的乘法结果:

hightable = [0 29 58 39 116 105 78 83 232 245 210 207 156 129 166 187]

 

将 100 (01100100)拆分,则:

100 = 0110 << 4 ⊕ 0100

 

在低位表中查询 0十0(4),得:

lowtable[4] = 64

 

在高位表中查询01十 (六),得:

hightable[6] = 78

 

将七个查询结果异或:

result = 64 ^ 78 = 1000000 ^ 1001110 = 1110 = 14

 

从地点的对照中,我们不难发现使用SIMD的新算法进步查表速度首要呈今后四个地点:

  1. 缩减了乘法表大小;

  2. 增长查表并行度(从3个字节到1陆依旧三十二个字节)

行使 SIMD
指令在大大下降了乘法表的局面包车型客车同时多了三次查表操作以及异或运算。由于新的乘法表每一局地只有1六 字节,大家能够万事大吉的将其放置于 XMM 寄存器中,从而选用 SIMD
指令集提供的通令来进行数据向量运算,将原先的逐字节查表创新为相互的对 1陆字节举行查表,同时异或操作也是 16字节并行的。除了这些之外,由于乘法表的壹体化规模的下降,在编码进度中的缓存污染也被大大减轻了,关于缓存的题目大家会在接下去的小节中展开更仔细的分析。

以上的估计进程以单个字节作为例子,上面大家共同来分析应用 SIMD
技术对多个字节实行演算的进度。基本步骤如下:
拆分保存原有数据的 XMM 寄存器中的数据向量,分别存款和储蓄于不一致的 XMM 寄存器中

依据拆分后的数码向量对乘法表举行重排,即获得查表结果。大家能够将乘法表掌握为按梯次排泄的数组,数老董度为
1六,查表的进程能够驾驭为将拆分后的多寡(数据范围为 0-壹5)作为目录对乘法表数组进行重复排序。那样大家就足以因而排序指令完毕查表操作了将重排后的结果进行异或,得到最后的演算结果

以下是伪代码:

*// 将原始数据的右移4bit*

d2 = raw_data >> 4

*// 将右移后的数据的每字节与15(即1111)做AND操作,得到数据高位*

high_data = d2 AND 1111

*// 原始数据的每字节与15(即1111)做AND操作,得到数据低位*

low_data = raw_data AND 1111

*// 以数据作为索引对乘法表进行了重排*

for i, b = range low_data { low_ret[i]=low_table[b]}

for i, b = range high_data {high_ret[i]=high_table[b]}

*// 异或两部分结果得到最终数据*

ret = low_ret XOR high_ret

 

内需专注的是,要动用 SIMD 加快有限域运算,对 CPU 的最低需要是永葆 SSSE3扩大指令集。其余为了尽量升高功效,大家相应事先对数据开始展览内部存款和储蓄器对齐操作,在
SSSE三 下我们供给将数据对齐到 1陆Bytes,不然大家只可以使用非对齐指令进行多少的读取和写入。在那或多或少上比较优秀的是
Go 语言, 一方面 Go 支持间接调用汇编函数这为利用 SIMD
指令集提供了言语上的支撑;但其余1方面 Golang
又隐藏了内部存款和储蓄器申请的底细,那使得钦赐内部存储器对齐操作不可控,即便我们也得以通过
cgo 恐怕汇编来落到实处,但那扩大额外的担当。所幸,对于 CPU 来说一个 Cache
line
的深浅为6四byte,那在自然水准上得以援救大家减少非对齐读写带来的处置。其它,依据Golang
的内部存款和储蓄器对齐算法,对于较大的数据块,Golang 是会活动对齐到 3二 byte
的,因而对齐或非对齐指令的履行职能是相同的。

}

2 写缓存友好代码

缓存优化通过两地点拓展,其1是缩减缓存污染;其贰是增强缓存命中率。在品味成功那两点在此之前,大家先来分析缓存的主干工作规律。

CPU 缓存的暗中认可工作格局是 Write-Back,
即每三次读写内部存款和储蓄器数据都亟待先写入缓存。上文提到的 Cache line
即为缓存工作的中坚单位,其大小为固定的 6四 byte ,也就说即便从内部存款和储蓄器中读取
一字节的数码,CPU 也会将此外的陆三字节带入缓存。那样设计的由来首假设为了压实缓存的岁月局域性,因为所要执行的多寡大小平日远远当先这些数字,提前将数据读取至缓存有利于接下去的数量在缓存中被打中。

您就足以转化成一下不等式 {

贰.一 矩阵运算分块

矩阵运算的循环迭代中都用到了行与列,由此原始数据矩阵与编码矩阵的访问总有一方是非两次三番的,通过简单的大循环沟通并不可能改正运算的上空局域性。由此大家通过分块的章程来拉长期局域性来压缩缓存缺点和失误。

分块算法不是对三个数组的整行或整列实行操作,而是对其子矩阵展开操作,指标是在缓存中的数据被沟通此前,最大限度的应用它。

分块的尺寸不宜过大,太大的分块不能棉被服装进缓存;别的也不可能过小,太小的分块导致外部逻辑的调用次数大大上涨,发生了不供给的函数调用花费,而且也不能够丰富利用缓存空间。

S[2]-S[1]>=1-a;

贰.贰 裁减缓存污染

轻易窥见的是,编码矩阵中的周详并不会全盘覆盖全体 GF(2^八),例如 十+四的编码方案中,编码矩阵元帅验矩阵大小为
四×10,编码周到至多(恐怕会有重复)有十×四=肆拾2个。因而我们得以优先举办三个乘法表伊始化的进度,比如生成叁个新的2维数组来储存编码周到的乘法表。减少表的限量能够在读取表的经过中对缓存的传染。

除此以外在定义方法集时供给留意的是制止结构体中的成分浪费。防止将不须求的参数扔进结构体中,要是每3个办法仅使用个中若干个成分,则其它因素白白侵吞了缓存空间。

S[3]-S[2]>=-b;

三、 指令级并行与数据级并行的深深优化

本节第二介绍怎么着使用 AVX/AVX二指令集以及指令级并行优化来进一步进步质量表现。除外,大家还足以对汇编代码举行微调以博取微小的升级换代。比如,尽量幸免使用
陆风X8八-大切诺基一5 那 8个寄存器,因为指令解码会比其余通用寄存器多3个字节。但众多汇编优化细节是和
CPU 架构划设想计相关的,书本上甚至 Intel提供的手册也并无法提供最纯正的辅导(因为有滞后性),而且这个操作带来的效益并不明显,在此地就不做要紧表明了。

S[3]-S[4]>=c;

3.1 利用 AVX2

在上文中大家早就知晓怎样将乘法表拆分成 12八bits 的高低以适应 XMM
寄存器,那么对于 AVX 指令集来说,要丰盛发挥其效果,要求将乘法表复制到
256 bit 的 YMM 寄存器。为了做到那或多或少,大家得以选拔 XMM 寄存器为 YMM
寄存器的不及那壹风味,仅使用一条指令来形成表的复制(速龙 风格):
vinserti128 ymm0, ymm0, xmm0, 1

那条指令成效是将 xmm0 寄存器中的数据拷贝到 ymm0 中,而剩余 12八 位数据通过
ymm0 获得,个中马上数 一 标志 xmm0 拷贝的目标地是 ymm0
的上位。那条指令提供了三个 source operand(源操作数)以及二个destination operand(目的操作数),我们在此间运用 ymm0
寄存器同时作为源操作数
和对象操作数
来落成了表的复制操作。接下来我们便得以采用与 SSSE三下1致的诀窍来展开单指令 32 byte 的编码运算进度了。

由于使用了 SSE 与 AVX 那二种扩张指令集,大家须要制止 AVX-SSE Transition
Penalties[3]。之所以会有那种性质惩罚重借使由于 SSE 指令对 YMM
寄存器的上位一无所知,SSE 指令与 AVX 指令的混用会导致机器不断的施行 YMM
寄存器的上位保存与还原,那大大影响了质量表现。假诺对指令不熟悉,难以幸免指令混用,那么能够在
RET 前应用 VZEROUPPE揽胜极光 指令来清空 YMM 寄存器的要职。

三.2 指令级并行 (ILP) 优化

先后分支指令的开发并不仅为命令执行所供给的周期,因为它们可能影响前端流水生产线和当中缓存的内容。大家能够经过如下技术来减弱分支指令对品质的影响,并且抓牢分支预测单元的准确性:

  1. 少的运用分支指令

  2. 当贯穿 (fall-through) 更大概被执行时,使用向前条件跳转

  3. 当贯穿代码不太可能被执行时,使用向后条件跳转

迈进跳转平时用在自作者批评函数参数的代码块中,假若大家防止了传播长度为 0
的数目切片,那样能够在汇编中去掉相关的道岔判断。在自个儿的代码中仅有一条向后条件跳转指令,用在循环代码块的最底层。要求专注的是,以上
二 、 三点中的优化措施是为着契合静态分支预测算法的渴求,可是在商海上依据硬件动态预测方法等电脑占主导地位,因而那两点优化恐怕并不会起到增强分支预测准确度的效益,更加多的是卓绝的编制程序习惯的题材。

对此 CPU
的实践引擎来说,其往往包罗七个执行单元实例,那是实践引擎并发执行多个微操做的基本原理。其它CPU
内核的调度器下会挂有八个端口,那意味着每种周期调度器能够给执行引擎分发多个微操作。因而我们可以行使循环展开来加强指令级并行的也许。

循环展开正是将循环体复制数十次,同时调整循环的平息代码。由于它减弱了分段判断的次数,由此得以以往自不一样迭代的一声令下放在一起调度。

自然,如若循环展开知识不难地展开指令复制,最终动用的都以同一组寄存器,恐怕会妨碍对循环的有效调度。因而大家理应合理分配寄存器的选择。其它,若是循环规模较大,会招致指令缓存的缺点和失误率上涨。英特尔的优化手册中提议,循环体不应有当先 500 条指令。[4]

}

四、 小结

如上内容比较完好的还原了纠删码引擎的完成进程,涉及到了较多的数学和硬件层面包车型大巴学问,对于大多数工程师来说恐怕相对不熟悉,大家期望由此本体系作品的牵线能够为我们的工程进行提供多少支持。但受限于篇幅,很多情节不恐怕周详开始展览。比如,部分数学工具的辩白与认证并不曾到手详细的分解,还亟需读者通过别的专业材料的来进行更深远的读书。

附录:
Galois Fields and Cyclic Codes
http://user.xmission.com/~rimrock/Documents/Galois%20Fields%20and%20Cyclic%20Codes.pdf

有限域相关总结 https://github.com/templexxx/reedsolomon/tree/master/mathtools

Avoiding AVX-SSE Transition Penalties
https://software.intel.com/en-us/articles/avoiding-avx-sse-transition-penalties

Intel 64 and IA-32 Architectures Optimization Reference Manual :3.4.2.6
Optimization for Decoded ICache

 

根据地点的估计,出现x-y>=-z,就从x到y连一条边权为z的边,然后用BF或许SPFA跑最短路就吼辣!

并发无解的话正是跑出了负权环,SPFA松弛次数超越n次正是了。

只顾求最大值和微小值时的连边是不一致的,本人小心一下就好了。

是的是能够保障的(但是笔者好像在那一个算法上边看到了影射学的觉得?)。

 

那种鬼里鬼气的算法一般都以套路,出现了不是套路的标题就只可以重复分析或然GG了。

板子都是很好打客车,1个连边3个SPFA就能够实现了。可是实际接纳起来,就不是那么可爱了。

下边作者来搞几道标题,讲多少个套路。

 

1.POJ1201
Intervals
题目大意:给你n组约束(l,r,c),表示区间 [l,r]
上至少有c个点。问您壹起至少某些许个点?

 

那道题还是很simple的,记一个前缀和S[i]表示
[0,i] 上有多少个点。

然后二个约束就象征
S[r]-S[l-1]>-c;

还有八个相比较含蓄的条(tao)件(lu):

S[i+1]-S[i]>=0;

S[i+1]-S[i]<=1;

连完边跑1回就好了,负权环都不要判。

小结:差分约束的大规模套路—前缀和,相邻点的牢笼。

#include    <iostream>
#include    <cstdio>
#include    <cstdlib>
#include    <algorithm>
#include    <vector>
#include    <cstring>
#include    <queue>
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;

const int N = 50010;
struct Node{int to,val,next;}E[N<<4];
int far[N],head[N],tot,In[N],m,MIN=N,MAX=0;

int gi()
{
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
  return x*res;
}

LL gL()
{
  LL x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
  return x*res;
}

inline void link(int u,int v,int c)
{
  E[++tot]=(Node){v,c,head[u]};
  head[u]=tot;
}

inline void SPFA()
{
  memset(far,127/3,sizeof(far));
  queue<int>Q;Q.push(MAX);
  far[MAX]=0;In[MAX]=1;
  while(!Q.empty()){
    int x=Q.front();Q.pop();
    for(int e=head[x];e;e=E[e].next){
      int y=E[e].to;
      if(far[x]+E[e].val<far[y]){
    far[y]=far[x]+E[e].val;
    if(!In[y])Q.push(y),In[y]=1;
      }
    }
    In[x]=0;
  }
  printf("%d\n",-far[MIN]);
}

int main()
{
  //freopen("input.txt","r",stdin);
  //freopen("output.txt","w",stdout);
  m=gi();
  while(m--){
    int u=gi(),v=gi(),c=gi();
    link(v,u-1,-c);
    MIN=min(MIN,u-1);
    MAX=max(MAX,v);
  }
  for(int i=MIN;i<=MAX;++i){
    link(i,i+1,1);
    link(i+1,i,0);
  }
  SPFA();

  /*fclose(stdin);
    fclose(stdout);*/
  return 0;
}

 

 

2.POJ 1275 Cashier
Employment

标题马虎:店里贰四时辰需要至少有景逸SUV[i]个服务员,多个伙计能够三番五次工作八钟头,现有n个服务员来应聘,告诉你他们发轫工业作的光阴,求最少要稍微个人。

 

又是远近著名的,前缀和差分。

记S[i]代表前i个小时1共招募了有点个人,T[i]意味着该时间来应聘的人数。

S[-1]=0;

那正是说就有壹对很好的不等式了 {

S[i]-S[i-1]>=0;

S[i]<=S[i-1]+t[数学,i];

S[i]-S[j]>=R[i],      i>j 
且 i=j+8 mod 24;

}

但就像剩下的写不下来了?好像要知道S[24]的值?

悠闲大家能够枚举这么些值,记为Ans;

于是乎又有 {

S[i]-S[j]>=R[i]-Ans,     
i<j  且 i=j+八 mod 2四;(能够推一下)

永不忘了
S[23]-S[-1]>=Ans;(实际上应该是=)

}

怎么判断有未有解呢?出现了负权环正是无解了。

那样枚举肯定会T的。综上说述的那一个Ans有可二分性,能够就把枚举改成二分。

小结:二分答案+差分约束判负环是个美丽的事物。

   其实判负环没须要卡n次,卡到10~log次就基本上了惹。

#include    <iostream>
#include    <cstdio>
#include    <cstdlib>
#include    <algorithm>
#include    <vector>
#include    <cstring>
#include    <queue>
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;

const int N = 1010;
struct Node{int to,val,next;}E[N*N];
int n,m,head[N],tot,Ans,R[N],T[N];
int In[N],far[N],num[N];

int gi()
{
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
  return x*res;
}

inline void link(int u,int v,int c)
{
  u++;v++;
  E[++tot]=(Node){v,c,head[u]};
  head[u]=tot;
}

inline void addedge(int ans)
{
  memset(head,0,sizeof(head));tot=0;
  link(23,-1,-ans);link(-1,23,ans);
  for(int i=0;i<24;++i)
    link(i,i-1,0),link(i-1,i,T[i]);
  for(int j=0;j<24;++j){
    int i=(j+8)%24;
    if(i>j)link(i,j,-R[i]);
    else link(i,j,ans-R[i]);
  }
}

inline bool SPFA()
{
  memset(In,0,sizeof(In));
  memset(num,0,sizeof(num));
  memset(far,127/3,sizeof(far));
  queue<int>Q;Q.push(0);far[0]=0;
  while(!Q.empty()){
    int x=Q.front();Q.pop();In[x]=0;
    for(int e=head[x];e;e=E[e].next){
      int y=E[e].to;
      if(far[x]+E[e].val<far[y]){
    if(++num[y]>20)return false;
    far[y]=far[x]+E[e].val;
    if(!In[y])Q.push(y),In[y]=1;
      }
    }
  }
  return true;
}

int main()
{
  freopen("input.txt","r",stdin);
  freopen("output.txt","w",stdout);
  int Case=gi();
  while(Case--){
    memset(T,0,sizeof(T));
    for(int i=0;i<24;++i)R[i]=gi();
    int l=1,r=m=Ans=gi();
    for(int i=1;i<=m;++i)T[gi()]++;
    /*for(int i=l;i<=r;++i){
      addedge(i);
      if(SPFA())break;
      else Ans=i+1;
    }
    */
    while(l<=r){
      int mid=(l+r)>>1;
      addedge(mid);
      if(SPFA())r=mid-1;
      else l=mid+1;
    }
    if(l>m)printf("No Solution\n");
    else printf("%d\n",l);
  }
  return 0;
}

 

 

 

 

 还有一道题叫做服务器储存消息难题,是壹道有难度的难点,然则自身打算为它独自写一篇博客。

若是未有见到的话,大致是因为本人忘记那件事了还没写,因为小编困了…

 

 

相关文章

No Comments, Be The First!
近期评论
    分类目录
    功能
    网站地图xml地图