-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
60 lines (60 loc) · 49.1 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[CMU CacheLab]]></title>
<url>%2FCMU%20CacheLab.html</url>
<content type="text"><![CDATA[前言 最近做了一下CMU的CacheLab实验,感觉实验的整个C语言项目写得十分不错,以及实验考察内容十分有价值,特别是实验二中对64×64规模矩阵的优化令人称赞,加上好久没有更新博文了:cry:,于是想写一篇博文介绍一下,也方便日后回顾。本篇博文算是对与实验报告的归纳总结。 实验要求实验一要求 在第一部分中,需要实现一个Cache模拟器,根据输入的trace文件中对于各个数据的访问情况来模拟整个Cache的工作,其中trace文件是linux下valgrind工具获得。 在csim.c提供的程序框架中编写实现一个Cache模拟器,输入为内存访问轨迹trace文件,程序的功能需要模拟缓存相对内存访问轨迹的命中/缺失行为,输出为命中、缺失和缓存行淘汰/驱逐的总数。 完成的csim.c文件生成的可执行程序结果应与参考存储模拟器csim-ref在相同的命令行参数下产生一致的输出结果 模拟器必须在输入参数s、E、b设置为任意值时均能正确工作——即需要使用malloc函数(而不是代码中固定大小的值)来为模拟器中数据结构分配存储空间。 由于实验仅关心数据Cache的性能,因此模拟器应忽略所有指令cache访问(即轨迹中“I”起始的行) 假设内存访问的地址总是正确对齐的,即一次内存访问从不跨越块的边界——因此可忽略访问轨迹中给出的访问请求大小 main函数最后必须调用printSummary函数输出结果,并如下传之以命中hit、缺失miss和淘汰/驱逐eviction的总数作为参数: printSummary(hit_count, miss_count, eviction_count); 实验二要求 在第二部分中,需要完成矩阵转置的优化,由于Cache是对于局部性较好的程序性能更好,所以需要优化常规的矩阵转置程序让Cache的命中数尽可能的高而缺失以及替换次数尽可能的低。 实验的具体目的以及要求为:在trans.c中编写实现一个矩阵转置函数transpose_submit,要求其在参考Cache模拟器csim-ref上运行时对不同大小的矩阵均能最小化缓存缺失的数量。该函数的输入为两个二维数组A、B,函数的功能是将A进行矩阵转置后将结果存放到B中。 对于实现的限制要求有: 限制对栈的引用——在转置函数中最多定义和使用12个int类型的局部变量,同时不能使用任何long类型的变量或其他位模式数据以在一个变量中存储多个值。原因是实验测试代码不能/不应计数栈的引用访问,而应将注意力集中在对源和目的矩阵的访问模式上。 不允许使用递归。如果定义和调用辅助函数,在任意时刻,从转置函数的栈帧到辅助函数的栈帧之间最多可以同时存在12个局部变量。例如,如果转置函数定义了8个局部变量,其中调用了一个使用4个局部变量的函数,而其进一步调用了一个使用2个局部变量的函数,则栈上总共将有14个变量,则违反了本规则。 转置函数不允许改变矩阵A,但可以任意操作矩阵B。 不允许在代码中定义任何矩阵或使用malloc及其变种。 实验的测试数据有三组,在实验中可能针对这三种数据规模的数据进行特殊优化。 实验环境 项目 版本 操作系统 Ubuntu 18.04 LTS 64位 编译器 Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0 make GNU Make 4.1 内存调试工具 valgrind-3.13.0 版本管理器 git version 2.21.0 实验源文件详解cachelab.c CacheLab功能函数功能源码文件。结构体及函数如下: printSummary:打印Cache模拟结果,包括命中次数、缺失次数、替换次数 initMatrix:初始化两个矩阵,用于第二个实验 randMatrix:随机生成一个矩阵,试验中没有用到 correctTrans:正确转置函数,被第二个实验validate函数调用用于验证优化后的转置函数是否正确 rigisterTransFunction:注册转置函数,根据传入的转置函数指针以及函数描述字符串注册函数,并将该函数的cache模拟结果成员变量以及是否正确转置位初始化(hits,misses,evitions,correct)。 cachelab.h cachelab.c头文件,包括函数定义以及注册函数结构体定义。 csim.c 实验一文件,根据存放程序对数据的访问记录的输入文件内容,模拟cache,获得模拟结果。变量与函数解析如下: 变量解析 verbosity:是否输出每一行记录的命中、缺失、替换情况,一般用于bug测试 s:组索引组号位宽 b:cache每一行存放的数据位宽,即块内偏移位宽 E:关联度,即每组的行数,每组内是全相联 trace_file:数据访问追踪记录文件路径 S:组索引组数,由位宽计算得到 cache:模拟cache的一个二维数组,根据组号以及组内行号确定一行cache set_index_mask:用于获得组号的一个mask 函数解析 initCache:根据S,E创建cache数据,并初始化set_index_mask freeCache:回收cache模拟数组空间 accessData:根据传入地址,模拟访问一次数据,更新模拟结果 replayTrace:根据trace文件模拟整个数据访问,有的记录需要两次访问数据 printUsage:打印帮助信息 main:处理传入的参数,将参数传入函数,依次调用initCache、replayCache、freeCache、printSummary模拟Cache trans.c 实验二文件,存放多个转置函数,包括需要提交的函数(根据函数描述来确定是否未提交的结果函数),还包括注册函数,将多个转置函数注册到实验二测试集合中。 is_transpose:判断是否转置成功,但是所有代码中都没用到该函数,应该是编写trans时的测试函数 transpose_submit:最终提交的你的转置优化函数,最终算分也是根据这个函数的测试结果算分,其对应的函数描述为Transpose submission trans:最简单的没有任何优化的转置函数,函数描述为Simple row-wise scan transpose registerFunctions:注册所有转置函数,可以自行添加新的转置函数用于中间测试 tracegen.c 跟踪记录文件生成 根据转置函数对数据的访问生成数据访问记录文件,这样csim就可以根据文件模拟,输出的MARKER_START、MARKER_END的地址,输出文件名为.marker。 变量解析 func_list:转置函数列表,用于存放每个转置函数的入口地址、函数描述、正确性以及模拟结果,从cachelab.c中extern得到 func_counter:注册的转置函数个数,从cachelab.c文件extern得到 MAKER_START、MARKER_END:volatile类型,每次访问都要直接访存,所以在调用转置函数前后分别对MARKER_START、MARKER_END进行访问,这样在追踪的时候就可以根据MARKER_START、MARKER_END确定转置函数的追踪记录(因为追踪是对整个程序进行追踪) A:待转置矩阵 B:转置结果矩阵 M:矩阵列数 N:矩阵行数 函数解析 validate:调用correctTrans获得A对应正确转置结果矩阵,并和B进行对比判断优化的转置函数是否正确 main:处理命令行参数,依次调用registerFunctions、initMatrix、fprintf(输出MARKER_START、MARKER_END的地址)、调用命令行参数指定的转置函数(调用前后要访问MARKER_START、MARKER_END)。 test-trans.c 用于实验二测试每个注册的转置函数,并输出cache模拟结果。 eval_perf:评估表现函数,评估注册的转置函数的性能 执行的流程: 循环测试每个注册的转置函数,首先调用tracegen函数测试当前循环转置函数,用valgrind命令对程序进行追踪这样会获得整个tracegen函数的跟踪记录trace.tmp以及标记文件.marker trace.tmp文件会有所有数据访问的结果,但是由于tracegen程序在调用转置前后访问了volatile变量MARKER_START、MARKER_END,所以在转置函数的数据访问跟踪记录前后会有MARKER_START、MARKER_END的跟踪记录,根据.marker文件中MARKER_START、MARKER_END在运行的时候的数据地址就可以定位一个访问区间,这个区间内的访问记录就是转置函数的数据跟踪记录,但是有指令的访问记录,所以只要将区间内指令的访问记录删除就可以获得转置函数的数据访问记录,将该记录输出到trace.fn文件(n为当前当前测试转置函数编号) 调用csim-ref根据trace.fn跟踪文件模拟cache以测试转置函数表现,并根据这个输出结果,csim-ref与自己写的csim区别在于它不仅是正确的cache模拟程序,还将模拟结果输出到了件.csim_results,这样在调用完csim-ref程序之后,当前循环后面的代码可以读取这个文件获得运行结果用以评估表现 uasge:帮助信息输出函数 sigsegv_handler:SEGSEGV信号处理函数,处理段错误 sigalrm_handler:SIGALRM信号处理函数,处理超时错误 main:处理命令行参数,包括矩阵大小以及是否显示帮助信息然后会安装信号处理函数,处理子程序错误,并设置时钟,以防子程序死循环或者发生段错误,然后会调用eval_perf函数对所有注册的函数进行评估,并最后输出结果 程序执行流程实验一执行流程 运行test-csim测试csim 运行csim的执行文件,传入不同的参数,根据traces文件夹下的各个追踪文件模拟cache,与csim-ref的正确结果进行比对并输出结果。 实验二执行流程 运行test-trans,测试命令行制定的矩阵大小 对每个注册函数进行测试,文件流程 每次测试输出测试结果 driver.py执行流程 运行test-csim测试实验一,获得实验一分数,共八个样例,除最后一个样例6分其他均3分 linux> ./csim -s 1 -E 1 -b 1 -t traces/yi2.trace linux> ./csim -s 4 -E 2 -b 4 -t traces/yi.trace linux> ./csim -s 2 -E 1 -b 4 -t traces/dave.trace linux> ./csim -s 2 -E 1 -b 3 -t traces/trans.trace linux> ./csim -s 2 -E 2 -b 3 -t traces/trans.trace linux> ./csim -s 2 -E 4 -b 3 -t traces/trans.trace linux> ./csim -s 5 -E 1 -b 5 -t traces/trans.trace linux> ./csim -s 5 -E 1 -b 5 -t traces/long.trace 运行test-trans测试实验二,获得实验二分数,共三个样例 32×32:如果m600得0分,对其他m得(600-m)*8/300分。 64×64:如果m2000得0分,对其他m得(2000-m)*8/700分。 61×67:如果m3000得0分,对其他m得(3000-m)*10/1000分。 计算两个实验得分得到总分,共53分 实验思路实验一思路 在第一部分中,需要修改的文件只有csim.c。在该文件中,包含的函数有: void initCache() void initCache() void freeCache() void accessData(mem_addr_t addr) void replayTrace(char* trace_fn) void printUsage(char* argv[]) int main(int argc, char* argv[]) 其中csim.c文件已经完成了初始化Cache的initCache函数、打印帮助信息的printUsage函数以及整个程序执行流程的main函数,在main函数中包含了对于命令行参数的解析以及对于各个函数的调用。需要完成的函数只有释放Cache空间的freeCache函数、模拟访问数据的accessData函数以及根据输入trace文件重现数据访问过程的replayTrace。 在本实验中设计到的数据存储结构为cache_line结构体,其存储内容为Cache一行的辅助信息位,由于是模拟并没有存放具体数据,包含有效位valid、标志信息位(数据地址)tag、LRU算法计数位lru。在该程序框架中可以看出是通过二维数据的方式模拟一个组相联的Cache,所以创建了一个该结构体的二维数组。 首先需要完成的就是释放为了模拟Cache而开辟空间,即freeCache函数,根据initCache源码可以知道,该二维数组是通过一个指针数据实现的,所以每一组存放的位置是连续的,但是组与组之间存放的位置并不连续,在释放的时候也需要对每一组进行单独释放。 接下来需要实现的是对于一次数据访问的模拟,即完成accessData函数。采取的实现思路是首先遍历一遍输入的数据地址对应的Cache组,即遍历组内E行Cache行,在遍历时候如果有效位为真则说明是有效的,此时判断标记位是否符合输入的地址,如果是那么就命中,否则就说明是其他数据则更新lru计数位;如果有效位不为真,说明是空闲位,说明可以用空闲位进行替换。在遍历时候记录下替换位,即lru计数位最小的位就是需要替换的位(因为当命中后会将lru置为最大,更新是每次减一)。遍历完之后如果方式缺失则根据替换信息位将需要替换的那一行信息更新为输入的数据地址对应的信息。在整个函数实现的过程中还需要根据verbosity位决定是否显示轨迹信息。本设计思路只需要遍历一遍,时间上较为有效率。具体流程图如下图所示: 最后需要完成的就是重新整个数据的访问过程,即完成replayTrace函数,在该函数中主要是分析读入的trace文件,由于每一行表示操作一次数据,所以只需要对每一行进行同样的处理即可,通过循环即可完成。每一行数据的格式为: [0-1 space] operation address,size Operation(操作):内存访问的类型。I - 指令装载,L - 数据装载,S - 数据存储,M - 数据修改(即数据装载后接数据存储) address:所64-bit十六进制内存地址 size:访问的内存字节数量 需要注意的是对于I指令由于在本实验中不考虑对于指令的模拟,知识针对数据Cache的一个模拟,所以在输入文件中已经过滤掉了对于指令的轨迹数据。对于M指令,由于是一个修改指令所以是读取了依次数据修改后存放回回去,所以这一条指令其实是房村了两次,所以要调用两次accessData函数。具体流程图实现如下图所示: 实验二思路 由于是针对矩阵转置的一个优化,所以需要知道原来的程序不好的地方在哪里。在trans.c程序中已经完成了一个简单的矩阵转置函数,函数描述为“Simple row-wise scan transpose”,其实现原理就是通过两重循环完成对应矩阵的转置。在这个程序中,其不足的地方有: B数组不同行之间的Cache替换:由于是先读取A矩阵的一行存放到B矩阵的一列,那么存放到B矩阵的一列时,由于数据存放是按照行优先所以需要访问B中每一行前面的数据,也就是说要将每一行前面的数据都要加载到Cache中。但是Cache的行数是有限的,能够存放的不同的B数组行的行数是有限的,如果存放不下B的所有行,当需要访问B的后面的行是就会将原来的B的行替换出来造成依次替换,而由于每一列B都要经历这个过程,所以说每一次B的数据访问都会造成一次替换,Cache的效率很低。 A数组与B数组之间的Cache替换:Cache中不仅存放了B数组的数据,还存放了A数组的数据以及一些函数的局部变量的数据,所以在访问B的某一个数据时,如果与Cache中A的数据属于同一组同一行那么A的数据就会被替换出去,再次访问A的时候就会再次造成缺失。 以上两点就是trans.c中未优化的矩阵转置函数的缺点,按照理想情况来讲,如果数据不存在替换的情况,即后面的数据不会替换前面还未访问完的数据时,Cache只会在第一次加载数据的时候产生一次缺失而不会有后面的那么多次不希望看到的替换,所以需要解决这两点。 在这之前,先来看一下程序以及输入的数据,在整个程序框架中通过读取源码可以发现测试程序采用的参数为s=5,E=1,b=5,也就是说采取了直接相连的方式,每组内行数只有一行。测试的三个矩阵的大小分别为32*32,64*64,61*67。 首先针对第一个不足之处,为了避免B数组后面的某一行数据将前面还未访问完毕的某一行数据进行替换,就需要缩小转置的范围,即让一次转置的行数减少,所以要根据矩阵的大小以及Cache的大小来计算B数组间隔多少行会产生数据属于同一组的情况。首先一个整型变量的大小为4B,一行Cache的存储的数据位数为5b,也就是25即32个字节,也就是说一行Cache能够存放8个整型变量,同理由于Cache的组数的位数为5,能够存放的行数为32行,那么对于实验测试的Cache每间隔32*8个整型变量的数据就属于同一行,如果在一次循环中读取了这样的两行机会发生替换造成Cache缺失数大量提升,所以需要将每次处理的数据控制在256个整型变量以内,对于32*32的矩阵,每一行有32个整型变量,所以每间隔256/32=8行就会发生如上所说的情况,所以需要将矩阵大小控制在8*8以内,以8*8规模的矩阵逐步转置32*32的矩阵,最终将其完全转置;对于64*64的矩阵,则是每间隔4行就会发生如上所述情况。对于61*67的矩阵,由于矩阵本身大小并不是2的次方,所以间隔8行的数据并不会发生替换,因为不是属于同一组,而Cache能够存放的行数还是较多的,只要不是存放在同一行Cache能够存放下更多的数据,所以可以将每次转置的矩阵规模变大一些保证不超过32的前提下找一个合适的值就可以了,在本设计中取的值是16。 对于第二个不足之处,即如果存在A和B的数据存放在同一组的情况时,如果需要访问其中一个的数据就会造成替换,采取的解决办法是通过临时变量的方式进行中转。结合第一个不足之处的解决方案,只讨论8*8的情况,因为所有的转置都是按照8*8的大小重复进行的。在矩阵的行数以及列数一定的情况下,只有对角线上的元素才会存在在A、B对应位置的数据属于同一组的情况,因为对角线上数据不论按行优先还是列优先计算偏移,对于行数和列数一样的矩阵来说其偏移量都是一样的,所以需要对含有对角线元素的矩阵进行特殊处理。处理的方式是通过临时变量的方式,在转置的过程中,外层循环是8*8矩阵的行号,内层循环是8*8矩阵的列号,转置A的一行的时候,如果直接将A对角线上元素赋值给B的对角线上元素时,B的对应块就会把A的该行替换掉,然后在A之后的赋值中又得替换回来,所以采取的方式是用临时变量暂存A对角线元素,然后待该行循环结束之后再赋值给B的对应位置,这个时候B还是会替换掉A的该行,但是已经不影响了,因为A该行的其他变量都已经赋值给B对应的位置了。 对于32*32和61*67的实现代码如下: 1234567891011121314for (i=0;i<N;i+=SQUARE_SIDE(N)) for (j=0;j<M;j+=SQUARE_SIDE(N)) { for (k=i;k<min(i+SQUARE_SIDE(N),N);k++) { for (l=0;l<min(SQUARE_SIDE(N),M-j);l++) if (k != j+l) B[j+l][k]=A[k][j+l]; else temp[0]=A[k][j+l]; if (i==j) B[k][k]=temp[0]; } } 对于32*32的测试,每一个8*8的缺失次数为(8+8)=16次,理想情况下不考虑与局部变量的冲突替换以及其他代码的数据访问总的缺失次数为16*4*4=256,加上其他原因导致的数据访问缺失,预计缺失次数应小于300符合题目要求。 对于64*64的测试,按照之前的计算,应该按照4*4的大小对64*64的矩阵进行转置,但是这又出现了一个新的问题,即由于每次只使用了A的一块中的4个整型变量,那么在第二次使用的时候就不得不重新加载新的整型变量了,同理每次只存放了4列B值,那么同一块中还有4个整型变量没有被赋值,所以下次使用的时候仍需要重新加载到Cache中,造成替换替换次数仍较多。 解决这个问题的方法就是用B的空间暂存A的数据,对于8*8的矩阵,将前4行A的数据全部赋值给B前4行的某些变量,这样之后就不用再次用到A的前4行了。然后用A的后四行以及B的前4行中暂存数据更新更新B的后4行并将B的前4行更新,但是要注意由于B的第i+4行会替换掉B的第i行,所以需要先将数据取出来并赋值成正确的值,然后再更新B的第i+4行,这样由于不会用到B的第i行了所以第i+4行替换第i行不会造成多余的替换。具体分为4步: 将A左上角转置赋值给B左上角,将A右上角转置赋值给B右上角将A的右上角转置后并没有像之前那样直接赋值给B的左下角因为跨了4行访问B中元素会造成多余的替换,所以用B的右上角暂存,这样也不用再访问A了,完成后的状态图如图2.1所示。这一步会缺失8次,即A的前四行以及B的前四行数据访问的Cache缺失。 将B右上角的第i行后4个整型存储到temp数组前4个整型,其中temp数据为B第i行的正确转置后的数据。如图2.2所示 将A的第i列后4个整型变量赋值给B第i行后4个变量,具体的状态如图2.3所示。可以看到已经完成了B矩阵第i行的数据的正确赋值,但是还没有访问B的第i+4行,所以目前只多缺失了4次即A的后4行替换前4行的四次。 将A第i+4列后4个整型赋值给temp数组的后四个整型,也可以不通过temp数组直接赋值但是对于对角线来说会造成缺失。所以还是存放到temp中,temp由于一直在使用,所有不会被替换出去除了与A、B某一块的数据访问冲突时。这里不会造成新的缺失。这一步的的状态图如图2.3所示。 将temp数组所有8个整型赋值给B第i+4行。这次赋值会造成Cache的缺失,即B的第i+4行会替换掉B的第i行,但是由于B的第i行已经完成转置了所以之后用不到了也就不存在缺失了。具体的状态图如图2.5所示。 重复2-5步骤直到将完成A的转置,在不考虑A、B之外的数据Cache冲突的情况下,8*8块的转置的总的缺失次数为(4+4+4+4)=16次,对于64*64的矩阵来说,理想情况下的缺失次数为16*8*8=1024,加上可能与局部变量之间的冲突以及除了转置函数循环之外的代码的数据访问,总的数据访问缺失次数应小于1300,预计符合题目要求。 根据以上设计,书写代码完成对于64*64大小矩阵的优化,最终优化代码如下: 1234567891011121314151617181920212223242526for (i=0;i<N;i+=8) for (int j=0;j<M;j+=8) { for (int k=i;k<i+4;k++) { for (l=0;l<8;l++) temp[l]=A[k][j+l]; for (l=0;l<4;l++) { B[j+l][k] = temp[l]; B[j+l][k+4] = temp[l+4]; } } for (int k=j+4;k<j+8;k++) { for (l=0;l<4;l++) temp[l]=B[k-4][i+l+4]; for (l=4;l<8;l++) temp[l]=A[i+l][k]; for (l=4;l<8;l++) B[k-4][i+l]=A[i+l][k-4]; for (l=0;l<8;l++) B[k][i+l]=temp[l]; }} 测试结果 运行python driver.py进行总体测试,结果如图所示。 后记 完整的代码点击Github查看。]]></content>
<categories>
<category>Course</category>
</categories>
<tags>
<tag>计算机组成原理</tag>
<tag>Cache</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Stable Matching-稳定匹配问题]]></title>
<url>%2FStable%20Matching-%E7%A8%B3%E5%AE%9A%E5%8C%B9%E9%85%8D%E9%97%AE%E9%A2%98.html</url>
<content type="text"><![CDATA[问题引入 在求职或者约会的情景中,我们经常会面临双向选择的问题,即一个男生十分喜欢一个女生但女生却不喜欢男生等等情况,在多个求职者和多个应聘者中也会存在这样情节。每个求职者会有自己偏好的公司,每个公司也有自己偏好的求职者,如果每个公司在面试完所有求职者之后按照前3名下发应聘书,由于不同公司可能同时相中同一个应聘者以及应聘者对各个公司的偏好不同,很容易造成应聘失败,这样招聘人数不达标的公司又不得不重新发新的应聘书,而新的应聘书可能导致之前已经答应去某个公司的应聘者取消前往计划,这样无疑会造成一系列混乱的发生,而且也浪费了很多时间。那么如果事先知道每个应聘者、每个公司的偏好的情况下,能否提供一种应聘方式让这种情况不再发生呢?稳定匹配的引入很好解决了这个问题。 问题抽象 给定两个集合 $M={m_1,m_2,\cdots,m_n}$,$W={w_1,w_2,\cdots,w_n}$ 对这两个集合:每个M中元素对W中所有元素都有一个排名、优先表(无并列) 每个W中元素对M中所有元素也有一个排名、优先表(无并列) 若M表示n个男生的集合,W表示n个女生的集合,试给出一种分配方式让每个男生和每个女生都能找到合适对象(什么分配是合适的?) 概念定义 为了方便解决问题,我们需要定义如下概念以帮助我们理解、界定问题。 以下定义中的m,m’表示M中两个不同元素,w,w’表示W中两个不同元素。 有序对:(m,w) M×W:笛卡儿积 表示M中元素与W中元素所有的有序对的集合 匹配:S是M×W的子集(有序对的集合),满足以下条件则称S为对M,W的一个匹配 + 每一个M和W中元素至多只出现在S集合的一个有序对中(可能不出现) 完美匹配:S’是M×W的子集,满足以下条件则称S’为对M,W的一个完美匹配 S’是一个匹配 S’满足M和W中每一个元素恰好只出现在S’的一个有序对中 不稳定因素:对于一个完美匹配,若存在两个有序对(m,w),(m’,w’)满足 m相较于w更偏爱w’ w’相较于m’更偏爱m则称有序对(m,w’)为S的一个不稳定因素 稳定匹配:对于M×W的集合S,当满足以下两个条件的时候则称S是一个稳定匹配 S是完美匹配 S中没有不稳定因素 有效伴侣:对于一个有序对(m,w),满足以下条件则称w是m的有效伴侣 存在一个稳定匹配,它包含(m,w)有序对 最佳有效伴侣:对于一个有序对(m,w),满足以下则成w是m的最佳有效伴侣 w是m的有效伴侣 在所有m的有效伴侣中w在m心中排名最高记作w=best(m) 最差有效伴侣:对于一个有序对(m,w),满足以下则成w是m的最差有效伴侣 w是m的有效伴侣 在所有m的有效伴侣中w在m心中排名最低记作w=worst(m) G-S算法(Gale-Shapley算法) 基本思想:以不断”求婚”的过程来逼近一个稳定匹配的状态 伪代码: 12345678910111213141516初始所有的m∈M和w∈W都是自由的While 存在男人m是自由的且没有对每个女人都求过婚 选择这样一个男人m 令w是m的优先表中m还没有求过婚的排名最高的女人 If w是自由的 then (m,w)变成约会状态 Else w当前正在与m'约会 If w相较于m更爱m' then m保持自由 Else w相较于m'更爱m (m,w)变成约会状态 m'变成自由状态 Endif EndifEndwhile输出已约会的集合S. G-S算法的一些性质或者特点 I. 对于任一W中元素w从第一次被求婚开始一直保持约会状态,且w正在约会的伴侣变得越来越好(按照w的优先表) II. 对于任一M中元素m求过婚的一系列女人会变得越来越差(按照m优先表) III. G-S算法在至多n2次While循环的迭代之后会终止 IV. 若m在算法执行的某个时刻是自由的,那么还存在一个w他还没有对其求过婚 V. 终止时返回的集合S是一个完美匹配 VI. 考虑G-S算法的一次执行,它返回一个对的集合S.集合S是一个稳定匹配 VII. G-S算法的每次执行都会得到同一个结合S* VIII. 在稳定匹配S*中每个女人都与他最差的有效伴侣配对 一些想法 在看完G-S算法伪代码之后我们必须要解开几个疑惑 a. G-S算法会不会无限进行下去,因为每次循环可能使两个人变成约会状态,但是也会使两个人从约会状态中解除,会不会陷入死循环。 b. G-S算法得到的集合S是不是一个稳定匹配 c. G-S算法每次执行得到的S是不是会不一样 d. 如果G-S算法得到的S是一个特定的稳定匹配,那S和众多可能的其他稳定匹配相比有什么特征 由1的一个疑惑我们可以看出之前的一些G-S特征或者定理的来由 a. 如果G-S算法不会无限进行下去,那么肯定存在一个逐渐逼近的过程 1) 所以我们可以分析得到在G-S算法执行的过程中的一些特点即性质1、 2) 我们会发现由于性质1、2的存在,G-S算法的执行不会永远进行下去得到性质3 b. 稳定匹配的条件是完美匹配以及不存在不稳定因素 1) 首先证明完美匹配,完美匹配的特点概括来讲就是”不重不漏” 首先说说证明的必要性,因为while循环退出的条件是不存在男人m是自由的且还没对每个女人都求过婚,那么存在几种情况i) 所有男人都是不自由的 ii) 存在有男人m是不自由的但是已经对于每个女人都求过婚 我们首先要做的就是证明S包含了所有男人女人即不漏(即排除第2种情况),然后讨论不重的问题a) 证明"不漏": 我们要排除第2种情况即需要说明男人只要是不自由的那么就还有女人没有被他求过婚即性质4 假设存在2中情况,那么由性质1可知所有女人都是约会状态,那么正在约会的人就是2n个人,但一共就只有2n个人,说明所有人都在约会,没有人是自由的,说明第2种情况不存在,性质4成立 b) 证明"不重" 由与约会都是两两进行,每个人总是同时与一个人约会,所以不会存在有重复现象 综合以上,我们发现得到的集合S做到了”不重不漏”,所以它是一个完美匹配即性质5 2) 接下来证明不存在不稳定因素 假设存在一对不稳定因素(m,w’),即S中存在两对有序对(m,w) (m’,w’),但是m相比于w更爱w’,w’相比于m’更爱m 考虑G-S的执行过程,对于m的求婚旅程,由于w’优先级更高,所以会先求婚w’,这个时候w’有两种状态,已经约会成功或者处于自由状态 a) 对于已经约会成功:假设其在m向w’求婚的时候w’正与m’’约会 此时m向w’求婚会出现两种情况,成功或失败i) 成功:说明m比m''在w'心中地位高,所以之后不论其他男生怎么向w'求婚,其排位都只会比m在w'心中高,但最终m'在w'心中排位比m低,矛盾。 ii) 失败:说明m''比m在w'心中地位高,所以之后不论其他男生怎么向w'求婚,其排位都只会比m''在w'心中高,但最终排位却是m'<m<m'',矛盾。 b) 对于处于自由状态:m直接”牵手成功”,所以之后不论其他男生怎么向w’求婚,其排位都只会比m在w’心中高,但最终m’在w’心中排位比m低,矛盾。 综上,我们可以得出是不可能存在这么一对不稳定因素。 S既是完美匹配又没有不稳定因素,所以S为一稳定匹配所以我们可以得到性质6 在知道G-S算法得到的是一个稳定匹配之后,我们需要考虑的就是是否每次执行都会得到同一个稳定匹配,为什么要思考这样一个问题呢?因为G-S算法其实可以异步实现,现实生活中不就是存在多个男生向多个女生同时求婚的情况吗?那么如果异步实现必定由于每个线程执行情况的不同带来求婚的先后次序不是固定的,所以这样会不会导致最终结果的不同呢?这里不得不佩服前人敏锐的思考了,有效伴侣的提出很好的解决了这样一个问题。考虑证明最终得到的都是S* = {(m,best(m)):m∈M} (这个考虑十分难想得到)有两点值得注意: S*可能不是一个完美匹配,因为有效伴侣是只要有一个稳定匹配存在该有序对就是有效伴侣 但是对于多个男生来说最佳有效伴侣可能是同一个人,所以可能做不到”不重不漏” 如果这是一个完美匹配,该匹配是十分偏心的,是偏向于男生的证明过程:假设得不到这样一个集合S*而得到一个集合S,即存在一个男生他最终的伴侣不是最佳有效伴侣,也就是说在G-S算法执行过程中,存在至少一次被最佳有效伴侣拒绝的过程,将所有拒绝中第一次拒绝的男生即为m,其最佳有效伴侣为w=best(m)。(由于先后次序关系,男生如果被有效伴侣拒绝会最先被最佳有效伴侣拒绝,所以此次拒绝也是男生被女生所有拒绝中第一次被有效伴侣拒绝),由于最佳有效伴侣在所有有效伴侣中的排名最高,所以m在向有效伴侣求婚时会第一个向w求婚,然而他被拒绝了(不论是约会之后不久还是求婚当时),即存在一个男生m’比m在w心中排名更高,那么在包含(m,w)有效伴侣的稳定匹配S’中,假设m’与w’≠w配对,回到G-S算法得到的稳定匹配S中。由于w拒绝m是所有对最佳有效伴侣拒绝的第一个,之前还没人被有效伴侣拒绝,m’肯定也没有被有效伴侣拒绝,也就是说w拒绝m可能是因为w正在和m’约会或者m’向w求婚,不管什么情况m’肯定都是先向w求婚然后可能再向w’求婚,不可能先向w’求婚然后拒绝w’再向w求婚,因为(m’,w’)是有效伴侣而之前没有有效伴侣被拒绝。所以在m心中w的排名比w’高,综上可知(m’,w)为稳定匹配S’中一个不稳定因素,矛盾,说明假设不成立,得到性质5。 简单来说就是: 假设G-S没有得到S*得到了S → 存在一个男生伴侣不是最佳有效 → 存在第一次对最佳有效伴侣的拒绝(w拒绝m选择m’) → 存在稳定匹配S’这次m’选择了w’≠w → 这次拒绝之前没有任何对最佳有效伴侣以及有效伴侣的拒绝 → m’没有被拒绝 → m’不可能先选w’然后被拒绝了选w → m’先选w → m’更爱w w更改m’ → 稳定匹配S存在不稳定因素 → 矛盾 → G-S得到了S* 这也间接解决了第一点注意,即S*为一个完美匹配因为S*为G-S算法得到结果,该结果为稳定匹配,S*当然为完美匹配。 S*既然是G-S算法得到的唯一稳定匹配,从可能存在的若干个稳定匹配中脱颖而出肯定具备一定特点。在之前讨论中已经发现S*是偏向于男生的,因为每个配对的女生都是其最优匹配,那么对于女生来说这个配对意味什么呢? 根据性质8我们已经知道结论:在稳定匹配S*中每个女生都与他最差的有效伴侣配对 证明过程:假设S*中存在有序对(m,w)使得m不是w的最差有效伴侣,那么肯定存在一个稳定匹配S’,w在这个稳定匹配中和更不喜欢的男生m’配对,w更喜欢m,而m与另一个女生w’=w配对,而w为m的最佳有效伴侣,w’为m的有效伴侣,所以m更喜欢w,那么在稳定匹配S’中(m,w)即为不稳定因素与S’为稳定匹配矛盾。 故得到性质8 问题解决G-S算法实现代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374#include<iostream>using namespace std;const int N = 10005;int W[N]={0},M[N]={0};int M_pri[N][N]={0},W_pri[N][N]={0};void getPri(int );void G_S(int );void output(int );bool lovemore(int ,int ,int);int main(){ int n; cin>>n; getPri(n); G_S(n); output(n); return 0;}void getPri(int n){ for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) cin>>M_pri[i][j]; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) cin>>W_pri[i][j];}bool lovemore(int m,int w,int num){ for (int i=1;i<=num;i++) if (W_pri[w][i] == W[w]) return false; else if (W_pri[w][i] == m) return true;}void G_S(int num){ int m,w; while (M[0]!=num) { w=m=0; while (M[++m]!=0) ; w = M_pri[m][++M_pri[m][0]]; if (W[w]) //如果女生已经在约会了 { if (lovemore(m,w,num)) { M[W[w]] = 0; M[m] = w; W[w] = m; } else continue; } else //如果女生没有约会还是自由状态 { M[m]=w;M[0]++; W[w]=m;W[0]++; } }}void output(int num){ for (int i = 1;i<=num;i++) cout<<"("<<i<<", "<<M[i]<<")"<<endl;} 样例测试样例输入1234567891011121314151617181920212223102 5 4 7 3 6 9 8 10 1 3 2 1 4 5 9 6 10 8 7 5 4 9 10 1 3 2 8 7 6 3 8 7 2 5 4 9 1 10 6 6 4 9 5 3 1 8 7 2 10 6 1 2 8 3 9 10 5 7 4 7 3 10 9 1 8 6 2 5 4 10 8 2 9 1 3 6 7 4 5 9 1 6 10 7 8 2 3 5 4 8 10 2 7 3 1 6 9 5 4 4 6 1 7 10 5 9 8 3 2 1 2 4 3 8 9 6 10 5 7 2 1 8 10 4 3 6 9 7 5 3 1 7 4 5 2 6 8 10 9 6 7 9 4 3 1 10 5 2 8 4 1 2 8 10 9 3 5 7 6 7 3 8 9 1 10 5 2 6 4 10 4 2 3 1 8 6 7 9 5 5 2 6 10 7 8 1 3 9 4 7 1 2 8 3 10 6 9 5 4 样例输出12345678910(1,2)(2,3)(3,4)(4,5)(5,6)(6,1)(7,7)(8,10)(9,9)(10,8)]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>算法</tag>
<tag>稳定匹配问题</tag>
<tag>算法设计与分析</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Nodepad++结合DOSBox配置一键编译运行]]></title>
<url>%2FNodepad%2B%2B%E7%BB%93%E5%90%88DOSBox%E9%85%8D%E7%BD%AE%E4%B8%80%E9%94%AE%E7%BC%96%E8%AF%91%E8%BF%90%E8%A1%8C.html</url>
<content type="text"><![CDATA[前言 最近在学习汇编语言程序设计,在最开始肯定是要配置环境啦,但是我们学的还只是8086系列的处理器的汇编,而现在的CPU是酷睿系列了,根本不支持,所以就需要用到DOSBox来模拟环境,然而这就遇到一个问题了:DOSBox编译链接运行程序十分麻烦,而且每次重新打开都需要输入一大堆东西,很是浪费时间,那么如何简单快捷的实现像写C,C++时那样一个快捷键源代码就自动编译运行了呢?于是就有了这篇博文。 配置环境 首先介绍一下实现环境: - DOSBox 0.74 模拟DOS系统 - Notepad++ v7.5.6 (64 bit) 用于编辑汇编源程序 - MASM 5.0 汇编程序编译器 - LINK 连接程序 - TD Turbor Debugger 调试器 这些软件需要自己下好并安装在对应位置,其中MASM LINK TD需要英文路径。 具体实现 实现的大致思路就是通过Notepad++运用批处理指令打开DOSBox 并在其中输入相关指令使源代码编译链接运行,实现编译运行调试的快捷指令需要三步 修改DOSBox配置文件首先打开以下路径: C:\Users\Mr. Xing\AppData\Local\DOSBox 然后就会在其中看到DOSBox的配置文件 dosbox-0.74.conf 用记事本打开在最下方的autoexec下输入MASM,LINK,TD的存放目录即 mount c d:\program\asmc: 然后在保存关闭,这样在运行DOSBox的时候就会首先执行这两行而不用重新输入。两行指令意思是 将d:\program\asm挂载为DOSBox下的c盘 进入c盘 配置编译运行修改完DOSBox的配置文件,接下来就需要配置编译运行快捷键了。首先打开Notepad++ 输入一段测试汇编源程序,例如:12345678910111213141516171819202122.386;----------------------------STACK SEGMENT STACK use16 DB 200 DUP(0)STACK ENDS;----------------------------DATA SEGMENT use16MSG DB 'How are you! $'DATA ENDS;------------------------------CODE SEGMENT use16 ASSUME CS:CODE,DS:DATA,SS:STACKBEGIN: MOV AX, DATA MOV DS, AX MOV DX, OFFSET MSG MOV AH, 9 INT 21H MOV AH,4CH ;exit INT 21H;-----------------------------CODE ENDS END BEGIN 然后点击菜单项的运行,如下图: 然后输入以下命令 D:\软件\DOSBox\DOSBox-0.74\DOSBOX.exe -c "@echo off" -c "mount d $(CURRENT_DIRECTORY)" -c "MASM d:\$(NAME_PART).asm;" -c "LINK $(NAME_PART).obj;" -c "COPY $(NAME_PART).exe d:\$(NAME_PART).exe" -c cls -c "d:\$(NAME_PART).exe" -c echo. -c pause -c exit 这段指令的意思如下: D:\软件\DOSBox\DOSBox-0.74\DOSBOX.exe DOSBox 软件运行程序文件地址 -c DOSBox command 命令 后跟一条指令且每条指令之前都要加-c @echo off 批处理指令 意思是关闭回显(这样在输入每条指令的时候就不会有c:\之类的) mount d $(CURRENT_DIRECTORY) 将当前汇编源程序挂在到DOSBox的d盘 MASM d:\$(NAME_PART).asm; 编译asm源程序生成二进制目标文件 LINK $(NAME_PART).obj; 链接二进制目标文件生成EXE文件 COPY $(NAME_PART).exe d:\$(NAME_PART).exe 将生成的EXE文件复制到源代码目录下 cls 清屏 d:\$(NAME_PART).exe 运行生成程序 echo. 换行 pause 暂停 exit 退出DOSBox $(FULL_CURRENT_PATH) 当前文件绝对路径 如d:\program\asm\demo.asm $(CURRENT_DIRECTORY) 当前目录 如d:\program\asm $(NAME_PART) 当前文件文件名 如demo $(FILE_NAME) 当前文件全名 如demo.asm $(EXT_PART) 当前文件扩展名如 asm 特别注意:如果你的源代码在E盘,那么请在输入指令的时候将源代码挂载到E盘这段程序只适合于保存到D:盘任何地方的asm文件 然后保存设置响应的名称和快捷键就OK了(我设置的是CTRL+ALT+B),具体运行效果如下: 配置调试同理只需要在运行框中输入以下批处理指令即可 D:\软件\DOSBox\DOSBox-0.74\DOSBOX.exe -c "mount d $(CURRENT_DIRECTORY)" -c "TD d:\$(NAME_PART).exe" 运行结果如下: 写在最后这次实现一键编译翻了许多文档,发现最全的还是官方文档,学习到了许多关于批处理的知识,收获还是蛮多的。还有就是由于指令长度的限制 在一键编译的时候并没有删除编译得到的obj和exe文件需要清理,有需要的小伙伴可以怎加一条指令del专门清理.obj 和 .exe如果有什么疑惑或者见解欢迎留言。]]></content>
<categories>
<category>Assembly</category>
</categories>
<tags>
<tag>汇编assembly</tag>
<tag>DOSBox</tag>
<tag>Notepad++</tag>
<tag>批处理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[盒子与小球]]></title>
<url>%2F%E5%B0%8F%E7%90%83%E4%B8%8E%E7%9B%92%E5%AD%90.html</url>
<content type="text"><![CDATA[Description of the Problem: 你有K个相同的盒子,N个互不相同的物品。你准备把这N个物品装入K个盒子,每个盒子最少要放入一个物品。问一共会有多少种分配方法。由于方案数很大,只需要输出方案总数除以10000的余数。 INPUT: 第一行有一个正整数 t ,表示数据组数(不多于50)。每组数据仅一行,两个整数, N 和K,其中1≤N ≤ 10^9,K≤min(50,N)。 OUTPUT: 每行输出一个整数,为方案总数除以10000的余数。 Analysis: 这是一个来自于同为HUSTer的高中童鞋的问题,从这个问题描述中,我们可以知道把N个物品放入K个相同的盒子的方法数就是 把N-1个物品放入K-1个盒子的方法数 加上 把N-1个物品放入K个盒子的方法数*K,为什么?我们只要考虑最后一个盒子和最后一个球即可,由于盒子是相同的,那么有两种情况 1.最后一个盒子只有一个球且为第N个球,即前面N-1个球没有填满K个盒子,最后一个球只能放在空盒子里。这种情况方法数就等于把N-1个物品放入K-1个盒子的方法数。 2.前面N-1个球填满的K个盒子,那么最后一个球可以任意放。这种情况方法数就等于 把N-1个物品放入K个盒子的方法数*K。 所以如果用f(N,K)来表示 N个物品K个盒子的方法数,可以得到以下递推式 f(N,K)=f(N-1,K-1)+f(N-1,K)*K;(即第二类斯特林数) 所以现在已经可以用程序通过递推算出结果(递归也可以但要慢一些,即使是记忆化递归)。但问题解决了吗? 答案是:NO。由于N很大所以要一项一项递推计算f(N,K)肯定会超时应为至少要算N*K次,所以显然要找更快速的算法让时间复杂度降低O(logn*K)。我们这时可以运用矩阵+快速幂的形式来解决这个问题。 首先来解释一下什么是快速幂:举个例子,对于计算q^k,我们其实可以将之看成q^[a(n)*2^n+a(n-1)*2^(n-1)+……a(1)*2^1+a(0)*2^0],其中a(n)=0或1。即把k看成许多{2^m}中某n+1项之和,那么怎么快速计算q^k呢?我们先把q^k看成n+1项之积,然后我们可以对k不断除2取余,如果第i次除2后余数为1,那么表示a(i-1)=1,即有n+1项乘积中有q^(i-1)这一项,具体描述步骤如下 首先我们用让一个数ans=1,用来存答案,用一个数x来存q^(i-1),x开始为1,然后先对k进行第一次除以2,如果余数为1,证明a(0)=1,有q^0这一项,于是让ans*=x,如果不为1,证明不存在这一项(或者说着这一项为1),就不用乘,然后进行完上述操作,x*=q; 然后我们对k进行第二次除以2,此时x=q^1,如果余数为1,证明a(1)=1,有q^1这一项,于是让ans*=x,如果不为1,证明不存在这一项(或者说着这一项为1),就不用乘,然后进行完上述操作,x*=q;所以在对k进行第i次除以2操作时,x=q^(i-1), 如果余数为1,证明a(i-1)=1,有q^(i-1)这一项,于是让ans*=x,如果不为1,证明不存在这一项(或者说着这一项为1),就不用乘,然后进行完上述操作,x*=q;最后如果k=0证明已经除完了,ans已经算完那么就得到了结果,结束运算。 现在来分析一下算法效率,若按照原算法计算q^k要乘k次也就是计算k次,用现在的快速幂算法,则要除以[log2(k)]+1次,也就是x要乘[log2(k)]+1次,而如果a(n)均为1,那么要ans也要乘 [log2(k)]+1,所以最多算2{[log2(k)]+1}次算法效率为O(logk)。 现在来讲一讲矩阵乘法: 定义:设A为m×p的矩阵,B为p×n的矩阵,那么称m×n的矩阵C为矩阵A与B的乘积,记作C=A×B 计算方法:乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。 性质:乘法结合律:(AB)C=A(BC) 所以我们可以把原问题的递推式子变成一个矩阵递推式$$\begin{equation}\begin{pmatrix}f(n,k)\\f(n,k-1)\\\vdots\\f(n,2)\\f(n,1)\\\end{pmatrix}={\begin{pmatrix}k&1&0&\cdots&0&0\\0&k-1&1&\cdots&0&0\\\vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&0&\cdots&2&1\\0&0&0&\cdots&0&1\\\end{pmatrix}}{\begin{pmatrix}f(n-1,k)\\f(n-1,k-1)\\\vdots\\f(n-1,2)\\f(n-1,1)\\\end{pmatrix}}\end{equation}$$ 所以可以用矩阵递推式把原递推式变成等比数列形式那么可以得到以下式子$$\begin{equation}\begin{pmatrix}f(n,k)\\f(n,k-1)\\\vdots\\f(n,2)\\f(n,1)\\\end{pmatrix}={\begin{pmatrix}k&1&0&\cdots&0&0\\0&k-1&1&\cdots&0&0\\\vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&0&\cdots&2&1\\0&0&0&\cdots&0&1\\\end{pmatrix}}^{(n-k)}{\begin{pmatrix}f(k,k)\\f(k,k-1)\\\vdots\\f(k,2)\\f(k,1)\\\end{pmatrix}}\end{equation}$$ 然后我们将快速幂与矩阵乘法递推式结合起来,我们只需要先用递推计算出base矩阵,然后用快速幂算出per矩阵的(n-k)次方两者相乘得到ans 其中我们将ans矩阵不再初始化为1,而是初始化为矩阵中的“1”(其他矩阵相乘等于其他)。$${\begin{pmatrix}1&0&0&\cdots&0&0\\0&1&0&\cdots&0&0\\\vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&0&\cdots&1&0\\0&0&0&\cdots&0&1\\\end{pmatrix}}$$ Code:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include<iostream>#include<cstring>using namespace std;const int mod=10000;struct matrix{ long long m[66][66];};matrix per,ans,base,cal,unit; matrix mul(matrix a,matrix b,int x,int y,int z){ matrix c; for (int i=0;i<x;i++) for (int j=0;j<z;j++) { c.m[i][j]=0; for (int k=0;k<y;k++) c.m[i][j]=(c.m[i][j]+a.m[i][k]*b.m[k][j])%mod; c.m[i][j]=c.m[i][j]%mod; } return c;}matrix qpow(int n,int k){ matrix p=per; int ci=n-k; while (ci) { if (ci&1) { unit=mul(unit,p,k,k,k); ci--; } else { p=mul(p,p,k,k,k); ci>>=1; } } return unit;}void init(int k){ for (int i=0;i<50;i++) for (int j=0;j<50;j++) { if (i==j&&i<k) per.m[i][j]=k-i; else if (j-1==i&&j<k) per.m[i][j]=1; else per.m[i][j]=0; if(i==j) unit.m[i][j]=1; else unit.m[i][j]=0; } for (int i=0;i<k;i++) base.m[i][0]=cal.m[k-1][k-1-i];}void calculation(){ for (int i=0;i<50;i++) cal.m[i][i]=cal.m[i][0]=1; for(int i=1;i<50;i++) for(int j=1;j<i;j++) cal.m[i][j]=(cal.m[i-1][j]*(j+1)+cal.m[i-1][j-1])%mod;}int main(){ int t,n,k; cin>>t; calculation(); while (t--) { cin>>n>>k; init(k); matrix xi=qpow(n,k); ans=mul(xi,base,k,k,1); cout<<ans.m[0][0]<<endl; } return 0;} Addition:下面给出一部分结果 n k 1 2 3 4 5 6 7 8 1 1 2 1 1 3 1 3 1 4 1 7 6 1 5 1 15 25 10 1 6 1 31 90 65 15 1 7 1 63 301 350 140 21 1 8 1 127 966 1701 1050 266 28 1]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>算法</tag>
<tag>矩阵</tag>
<tag>快速幂</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2Fhello-world.html</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
</entry>
</search>