Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit。

本篇开始之前先来验证前篇blog的分析结果,编写下面的测试文件来进行对上篇三个job的输出进行读取:

package mahout.fansy.item;

import java.io.IOException;
import java.util.Map; import org.apache.hadoop.io.Writable; import mahout.fansy.utils.read.ReadArbiKV;
import junit.framework.TestCase; public class ReadPreparePreferenceMatrixJob extends TestCase { // 测试 ITEMID_INDEX 输出:
public void testITEMID_INDEX() throws IOException{
String path="hdfs://ubuntu:9000/user/mahout/item/temp/preparePreferenceMatrix/itemIDIndex/part-r-00000";
Map<Writable,Writable> map= ReadArbiKV.readFromFile(path);
System.out.println("ITEMID_INDEX=================");
System.out.println(map);
} // 测试 userVectors 输出:
public void testUSER_VECTORS() throws IOException{
String path="hdfs://ubuntu:9000/user/mahout/item/temp/preparePreferenceMatrix/userVectors/part-r-00000";
Map<Writable,Writable> map= ReadArbiKV.readFromFile(path);
System.out.println("USER_VECTORS================");
System.out.println(map);
} // 测试 ratingMatrix 输出:
public void testRATING_MATRIX() throws IOException{
String path="hdfs://ubuntu:9000/user/mahout/item/temp/preparePreferenceMatrix/ratingMatrix/part-r-00000";
Map<Writable,Writable> map= ReadArbiKV.readFromFile(path);
System.out.println("USER_VECTORS================");
System.out.println(map);
}
}

运行的结果如下:

ITEMID_INDEX=================
{102=102, 103=103, 101=101, 106=106, 107=107, 104=104, 105=105}
USER_VECTORS================
{1={103:2.5,102:3.0,101:5.0}, 2={101:2.0,104:2.0,103:5.0,102:2.5}, 3={101:2.5,107:5.0,105:4.5,104:4.0}, 4={101:5.0,106:4.0,104:4.5,103:3.0}, 5={106:4.0,105:3.5,104:4.0,103:2.0,102:3.0,101:4.0}}
RATING_MATRIX================
{102={5:3.0,2:2.5,1:3.0}, 103={5:2.0,4:3.0,2:5.0,1:2.5}, 101={5:4.0,4:5.0,3:2.5,2:2.0,1:5.0}, 106={5:4.0,4:4.0}, 107={3:5.0}, 104={5:4.0,4:4.5,3:4.0,2:2.0}, 105={5:3.5,3:4.5}}

由上面的结果可以看出确实和分析的一样。同时注意到hdfs://ubuntu:9000/user/mahout/item1/temp/preparePreferenceMatrix/numUsers.bin这个文件的大小是0k,同时里面看不到内容,所以对这个文件进行读取,编写下面的测试代码:

package mahout.fansy.item;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.mahout.common.HadoopUtil; import junit.framework.TestCase; public class TestHadoopUtils extends TestCase { // 测试HadoopUtil的writeInt方法
public void testWriteInt() throws IOException{
String path="hdfs://ubuntu:9000/user/mahout/item1/temp/preparePreferenceMatrix/numUsers.bin";
int numberOfUsers=5;
Configuration conf=new Configuration();
conf.set("mapred.job.tracker", "ubuntu:9001");
// HadoopUtil.writeInt(numberOfUsers, new Path(path), conf);
int number=HadoopUtil.readInt(new Path(path), conf);
System.out.println(number);
}
}

测试的结果是:5,这个是因为数据太小,然后HDFS就不显示了?

接着源码进行分析:

numberOfUsers = HadoopUtil.readInt(new Path(prepPath, PreparePreferenceMatrixJob.NUM_USERS), getConf());

源码首先把所有的用户数全部读取出来,然后进行下一个phase。进入下一个phase后,这里首先判断下user是否是-1,那么就是用UserVector的输出记录数作为user的值:

if (numberOfUsers == -1) {
numberOfUsers = (int) HadoopUtil.countRecords(new Path(prepPath, PreparePreferenceMatrixJob.USER_VECTORS),
PathType.LIST, null, getConf());
}

然后是下一个RowSimilarityJob类了,其作用应该是:calculate the co-occurrence matrix,计算共生矩阵(啥叫共生矩阵?请google。。。我也不知道),其调用代码如下:

ToolRunner.run(getConf(), new RowSimilarityJob(), new String[]{
"--input", new Path(prepPath, PreparePreferenceMatrixJob.RATING_MATRIX).toString(),
"--output", similarityMatrixPath.toString(),
"--numberOfColumns", String.valueOf(numberOfUsers),
"--similarityClassname", similarityClassname,
"--maxSimilaritiesPerRow", String.valueOf(maxSimilaritiesPerItem),
"--excludeSelfSimilarity", String.valueOf(Boolean.TRUE),
"--threshold", String.valueOf(threshold),
"--tempDir", getTempPath().toString()});
}

打开RowSimilarityJob,可以看到这个类同样继承AbstractJob类,那么直接进入run方法吧,可以看到这个方法里面含有三个shouldRunPhase,每个含有一个prepareJob函数,即这个run里面也有三个Job,下面来一个个看。

(1)// weightsPath

Job normsAndTranspose = prepareJob(getInputPath(), weightsPath, VectorNormMapper.class, IntWritable.class,
VectorWritable.class, MergeVectorsReducer.class, IntWritable.class, VectorWritable.class);

输入就是前面的RATING_MATRIX的输出,格式为:<key,value>   :   itemID-->vector[userID:prefValue,userID:prefVlaue,...];

(1.1)看mapper:

(1.1.1)setup函数,这个函数就是做一些初始化工作,包括vector:norms; nonZeroEntries; maxValues;,以及变量similarity和threshold。

(1.1.2)map函数,这个函数就是对vector和变量做各种操作:

protected void map(IntWritable row, VectorWritable vectorWritable, Context ctx)
throws IOException, InterruptedException { Vector rowVector = similarity.normalize(vectorWritable.get()); int numNonZeroEntries = 0;
double maxValue = Double.MIN_VALUE; Iterator<Vector.Element> nonZeroElements = rowVector.iterateNonZero();
while (nonZeroElements.hasNext()) {
Vector.Element element = nonZeroElements.next();
RandomAccessSparseVector partialColumnVector = new RandomAccessSparseVector(Integer.MAX_VALUE);
partialColumnVector.setQuick(row.get(), element.get());
ctx.write(new IntWritable(element.index()), new VectorWritable(partialColumnVector)); numNonZeroEntries++;
if (maxValue < element.get()) {
maxValue = element.get();
}
} if (threshold != NO_THRESHOLD) {
nonZeroEntries.setQuick(row.get(), numNonZeroEntries);
maxValues.setQuick(row.get(), maxValue);
}
norms.setQuick(row.get(), similarity.norm(rowVector)); ctx.getCounter(Counters.ROWS).increment(1);
}

首先,similarity.normalize()函数其实返回的还是vector本身,并没有做其他操作,因为使用的EuclideanDistanceSimilarity,这个类的normalize函数直接返回;while循环里面含有写入文件的操作,这个写入的格式其实就是<key,value>  -->  <userID,[itemid:prefValue],同时比较同一个项目的各个用户的评分prefValue,求出最大值放入maxValue中。接下来就是threshold的判断了,由于在实战中是按照默认的值,所以这两个值是相同的,所以if里面的代码就不执行了。设置这个应该是和reducer中的操作相关的;然后就是norms设置了,其中similarity.norm(rowVector)是返回rowVector中项的平方和。最后ROWS计数器自增1。

(1.1.3)cleanup函数,这个主要输出三行即可:

 protected void cleanup(Context ctx) throws IOException, InterruptedException {
super.cleanup(ctx);
// dirty trick
ctx.write(new IntWritable(NORM_VECTOR_MARKER), new VectorWritable(norms));
ctx.write(new IntWritable(NUM_NON_ZERO_ENTRIES_VECTOR_MARKER), new VectorWritable(nonZeroEntries));
ctx.write(new IntWritable(MAXVALUE_VECTOR_MARKER), new VectorWritable(maxValues));
}

分别是:

-2147483647 [0x80000001]  -->vector( maxValues)
-2147483646 [0x80000002] -->vector( nonzeroEntries)
-2147483648 [0x80000000] -->vector( norms)

由于threshold和NO_THRESHOLD相等,所以if里面的代码没有执行,所以maxValues和nonzeroEntries中的应该是空。

(1.2)看commbiner://MergeVectorsCombiner

(1.2.1)就一个reduce函数:

protected void reduce(IntWritable row, Iterable<VectorWritable> partialVectors, Context ctx)
throws IOException, InterruptedException {
ctx.write(row, new VectorWritable(Vectors.merge(partialVectors)));
}

这个是把相同的key整合,输出为<key,value>  -->    <userid,vector[itemid:prefValue,itemid:prefValue,...]

(1.3)reducer:

(1.3.1)setup:初始化三个变量的路径:norms; nonZeroEntries; maxValues;

(1.3.2)reduce:

protected void reduce(IntWritable row, Iterable<VectorWritable> partialVectors, Context ctx)
throws IOException, InterruptedException {
Vector partialVector = Vectors.merge(partialVectors); if (row.get() == NORM_VECTOR_MARKER) {
Vectors.write(partialVector, normsPath, ctx.getConfiguration());
} else if (row.get() == MAXVALUE_VECTOR_MARKER) {
Vectors.write(partialVector, maxValuesPath, ctx.getConfiguration());
} else if (row.get() == NUM_NON_ZERO_ENTRIES_VECTOR_MARKER) {
Vectors.write(partialVector, numNonZeroEntriesPath, ctx.getConfiguration(), true);
} else {
ctx.write(row, new VectorWritable(partialVector));
}
}

在reduce中先把还没有整合的vector[itemid:prefValue,itemid:prefValue,...]再整合一下,然后判断key是否是setup中的三个变量,是的话就把value值写入相应的文件,否则则直接输出即可,所以这个job一共产生了四个文件,一个是job的输出,其他三个就是三个变量文件的输出了。

job的输出路径是:weightsPath,格式:<key,value>   -->       <userid,vector[itemid:prefValue,itemid:prefValue,...]。

(2)//pairwiseSimilarityPath

调用代码:

Job pairwiseSimilarity = prepareJob(weightsPath, pairwiseSimilarityPath, CooccurrencesMapper.class,
IntWritable.class, VectorWritable.class, SimilarityReducer.class, IntWritable.class, VectorWritable.class);

可以看到它的输入就是上一个weightsPath的输出了,格式:<key,value>   -->       <userid,vector[itemid:prefValue,itemid:prefValue,...];
(2.1)mapper: //CooccurrencesMapper

(2.1.1)setup:初始化变量similarity;numNonZeroEntries;maxValues;threshold;,其中中间两个应该是空,最后一个应该是默认值,第一个是欧氏距离;

(2.1.2)map:

protected void map(IntWritable column, VectorWritable occurrenceVector, Context ctx)
throws IOException, InterruptedException {
Vector.Element[] occurrences = Vectors.toArray(occurrenceVector);
Arrays.sort(occurrences, BY_INDEX); int cooccurrences = 0;
int prunedCooccurrences = 0;
for (int n = 0; n < occurrences.length; n++) {
Vector.Element occurrenceA = occurrences[n];
Vector dots = new RandomAccessSparseVector(Integer.MAX_VALUE);
for (int m = n; m < occurrences.length; m++) {
Vector.Element occurrenceB = occurrences[m];
if (threshold == NO_THRESHOLD || consider(occurrenceA, occurrenceB)) {
dots.setQuick(occurrenceB.index(), similarity.aggregate(occurrenceA.get(), occurrenceB.get()));
cooccurrences++;
} else {
prunedCooccurrences++;
}
}
ctx.write(new IntWritable(occurrenceA.index()), new VectorWritable(dots));
}
ctx.getCounter(Counters.COOCCURRENCES).increment(cooccurrences);
ctx.getCounter(Counters.PRUNED_COOCCURRENCES).increment(prunedCooccurrences);
}

map首先把当前user的所有item以及评分对转换到一个数组中,然后根据item大小对数组进行排序;然后就是两层的for循环了,其中的if里面肯定是要执行dots. 。。。的,因为threshold和NO_THRESHOLD肯定是相等的。然后来说两层for循环是干什么的:针对用户2的所有评分项目排序后为[101:2.0,102:2.5,103:5.0,104:2.0],那么输出就是101-->[102:prefValue101*prefValue102,103:prefValue101*prefValue103,104:prefValue101*prefValue104],然后是102 --> [103:prefValue102*prefValue103,104:prefValue102*prefValue104],。。。以此类推。其中similarity.aggregate返回就是两个参数的乘积。

输出格式为   <key,value>   -->  <itemid,vector[itemid:pref*,itemid,pref*,...]

(2.2)combiner: //VectorSumReducer

(2.2.1) reduce函数:

protected void reduce(WritableComparable<?> key, Iterable<VectorWritable> values, Context ctx)
throws IOException, InterruptedException {
Vector vector = null;
for (VectorWritable v : values) {
if (vector == null) {
vector = v.get();
} else {
vector.assign(v.get(), Functions.PLUS);
}
}
ctx.write(key, new VectorWritable(vector));
}

这个就是把map的输出整理一下,把相同key的vector中对应的项相加,比如 102 --> [103:1prefValue102*1prefValue103,104:1prefValue102*1prefValue104] ,102 --> [103:prefValue102*prefValue103,104:prefValue102*prefValue104,105:prefValue102*prefValue105],那么整合就是102 --> [103:prefValue102*prefValue103+1prefValue102*1prefValue103,104:prefValue102*prefValue104+1prefValue102*1prefValue104,105:prefValue102*prefValue105]。

(2.3)reducer://SimilarityReducer

(2.3.1)setup:初始化 变量similarity;numberOfColumns;excludeSelfSimilarity;norms;treshold;,其中的numberOfColumns就是numberUsers(实战中是5),而excludeSelfSimilarity在调用的时候被设置为True了。

(2.3.2)reduce:

protected void reduce(IntWritable row, Iterable<VectorWritable> partialDots, Context ctx)
throws IOException, InterruptedException {
Iterator<VectorWritable> partialDotsIterator = partialDots.iterator();
Vector dots = partialDotsIterator.next().get();
while (partialDotsIterator.hasNext()) {
Vector toAdd = partialDotsIterator.next().get();
Iterator<Vector.Element> nonZeroElements = toAdd.iterateNonZero();
while (nonZeroElements.hasNext()) {
Vector.Element nonZeroElement = nonZeroElements.next();
dots.setQuick(nonZeroElement.index(), dots.getQuick(nonZeroElement.index()) + nonZeroElement.get());
}
} Vector similarities = dots.like();
double normA = norms.getQuick(row.get());
Iterator<Vector.Element> dotsWith = dots.iterateNonZero();
while (dotsWith.hasNext()) {
Vector.Element b = dotsWith.next();
double similarityValue = similarity.similarity(b.get(), normA, norms.getQuick(b.index()), numberOfColumns);
if (similarityValue >= treshold) {
similarities.set(b.index(), similarityValue);
}
}
if (excludeSelfSimilarity) {
similarities.setQuick(row.get(), 0);
}
ctx.write(row, new VectorWritable(similarities));
}

首先dots就是获得某个item的第一个combiner的value输出,由于在实战中使用的数据比较少,所以前面只是用了一个reducer,一个combinner,这样在combiner中的value就只有一条记录,所以两层while循环其实是和combiner的功能一样,都是把vector中对应的项加起来;normA就是item的评分的平方和(norms向量可以参考前面);接着的while循环是求item和item的相似度(等下详细讲);然后excludeSelfSimilarity是true(即不对自身计算相似度),所以直接设置自身的相似度为0。比如针对这样的combiner输出<item105,vector[item106:p*105*106,item107:p*105*107]最终的结果输出应该是这样的<item105,vector[item105:0,item106:simi*105*106,item107:simi*105*107]>

总结来说,这个输出路径是:pairwiseSimilarityPath,输出格式是<key,value> -->  <itemid,vector[itemid:simi,itemid:simi,...]>

(3)

Job asMatrix = prepareJob(pairwiseSimilarityPath, getOutputPath(), UnsymmetrifyMapper.class,
IntWritable.class, VectorWritable.class, MergeToTopKSimilaritiesReducer.class, IntWritable.class,
VectorWritable.class);

可以看到输入是上面(2)的输出,即pairwiseSimilarityPath,格式是<key,value> -->  <itemid,vector[itemid:simi,itemid:simi,...]>,输出是similarityMatrixPath.toString();

(3.1)mapper://UnsymmetrifyMapper

(3.1.1)setup:就设置maxSimilaritiesPerRow变量,这里知道这个参数是在哪里设置了,但是是做什么用的呢?看map函数吧

(3.1.2)map:

protected void map(IntWritable row, VectorWritable similaritiesWritable, Context ctx)
throws IOException, InterruptedException {
Vector similarities = similaritiesWritable.get();
// For performance reasons moved transposedPartial creation out of the while loop and reusing the same vector
Vector transposedPartial = similarities.like();
TopK<Vector.Element> topKQueue = new TopK<Vector.Element>(maxSimilaritiesPerRow, Vectors.BY_VALUE);
Iterator<Vector.Element> nonZeroElements = similarities.iterateNonZero();
while (nonZeroElements.hasNext()) {
Vector.Element nonZeroElement = nonZeroElements.next();
topKQueue.offer(new Vectors.TemporaryElement(nonZeroElement));
transposedPartial.setQuick(row.get(), nonZeroElement.get());
ctx.write(new IntWritable(nonZeroElement.index()), new VectorWritable(transposedPartial));
transposedPartial.setQuick(row.get(), 0.0);
}
Vector topKSimilarities = similarities.like();
for (Vector.Element topKSimilarity : topKQueue.retrieve()) {
topKSimilarities.setQuick(topKSimilarity.index(), topKSimilarity.get());
}
ctx.write(row, new VectorWritable(topKSimilarities));
}

首先看while循环,比如针对这样的输入<item105,vector[item105:0,item106:simi*105*106,item107:simi*105*107]>,ctx.write的内容就是<item106,vector[item105:simi*105*106]>,<item107,vector[item105:simi*105*107]>;后面的for循环和新定义的TopK的作用是把simi*105*106和simi*105*107做比较,然后按照一定的顺序进行排序输出(应该是大的拍前面,比如107的simi比较大,那么输出就应该是<item105,item107:simi*105*107,vector[item105:0,item106:simi*105*106]>);所以这个mapper是有两种类型的输出(这里的类型不是指key、value的类型)。

(3.2)combiner://MergeToTopKSimilaritiesReducer

(3.2.1)setup:和mapper的初始化一样;

(3.2.2)reduce:

protected void reduce(IntWritable row, Iterable<VectorWritable> partials, Context ctx)
throws IOException, InterruptedException {
Vector allSimilarities = Vectors.merge(partials);
Vector topKSimilarities = Vectors.topKElements(maxSimilaritiesPerRow, allSimilarities);
ctx.write(row, new VectorWritable(topKSimilarities));
}

这里的merge又把所有的项目整合了起来,就等于是和输入一样了(这里应该不是,所以这里还应该编写一个follow仿制代码测试一下),然后topKElements就是把所有的排序,然后输出,所以他的输出应该就是和(3.1.2)中说到的for和TopK共同的作用了吧。输出比如107的simi比较大,那么输出就应该是<item105,item107:simi*105*107,vector[item105:0,item106:simi*105*106]>;

(3.3)reducer:// MergeToTopKSimilaritiesReducer,在combiner中提到的reduce中的merge函数如果又把所有的整合一下的话,那么就没有多大的意义了,这里没有多大的意义是指mapper没有做两种类型的输出了,只输出第二种类型即可。这个还有待验证。。。

分享,成长,快乐

转载请注明blog地址:http://blog.csdn.net/fansy1990

mahout算法源码分析之Itembased Collaborative Filtering(二)RowSimilarityJob的更多相关文章

  1. mahout算法源码分析之Itembased Collaborative Filtering(四)共生矩阵乘法

    Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. 经过了SimilarityJob的计算共生矩阵后,就可以开始下面一个过程了,这个过程主要是共生矩阵的乘法 ...

  2. mahout算法源码分析之Itembased Collaborative Filtering(三)RowSimilarityJob验证

    Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. 本篇分析上篇的分析是否正确,主要是编写上篇输出文件的读取以及添加log信息打印相关变量. 首先,编写下面 ...

  3. mahout算法源码分析之Collaborative Filtering with ALS-WR 并行思路

    Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. mahout算法源码分析之Collaborative Filtering with ALS-WR 这个算 ...

  4. mahout算法源码分析之Collaborative Filtering with ALS-WR (四)评价和推荐

    Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. 首先来总结一下 mahout算法源码分析之Collaborative Filtering with AL ...

  5. mahout算法源码分析之Collaborative Filtering with ALS-WR拓展篇

    Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. 额,好吧,心头的一块石头总算是放下了.关于Collaborative Filtering with AL ...

  6. diff.js 列表对比算法 源码分析

    diff.js列表对比算法 源码分析 npm上的代码可以查看 (https://www.npmjs.com/package/list-diff2) 源码如下: /** * * @param {Arra ...

  7. Solr4.8.0源码分析(19)之缓存机制(二)

    Solr4.8.0源码分析(19)之缓存机制(二) 前文<Solr4.8.0源码分析(18)之缓存机制(一)>介绍了Solr缓存的生命周期,重点介绍了Solr缓存的warn过程.本节将更深 ...

  8. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  9. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

随机推荐

  1. JPA中的@MappedSuperclass

    说明地址:http://docs.oracle.com/javaee/5/api/javax/persistence/MappedSuperclass.html 用来申明一个超类,继承这个类的子类映射 ...

  2. UVa 557 (概率 递推) Burger

    题意: 有两种汉堡给2n个孩子吃,每个孩子在吃之前要抛硬币决定吃哪一种汉堡.如果只剩一种汉堡,就不用抛硬币了. 求最后两个孩子吃到同一种汉堡的概率. 分析: 可以从反面思考,求最后两个孩子吃到不同汉堡 ...

  3. IE6、IE7、IE8中overflow:hidden无效问题

    在做图片无缝滚动效果时遇到了这个兼容问题 div宽1000px高250px超出隐藏. 但在Firefox中正常,超出部分隐藏,但是在IE6.IE7.IE8.Sogou高速下都显示了出来.做了这么多年的 ...

  4. linq xml读取

    <?xml version="1.0" encoding="UTF-8" ?> <cache> <chatOld> < ...

  5. fmri降噪,利用spatial+temporal信息

    1.基于小波+高斯模型 <SPATIOTEMPORAL DENOISING AND CLUSTERING OF FMRI DATA>

  6. 最简单的视音频播放示例2:GDI播放YUV, RGB

    前一篇文章对“Simplest Media Play”工程作了概括性介绍.后续几篇文章打算详细介绍每个子工程中的几种技术.在记录Direct3D,OpenGL这两种相对复杂的技术之前,打算先记录一种和 ...

  7. B树索引和位图索引的区别!

    B树索引主键和唯一性约束字段的B树索引,效率几乎和海量数据没有关系. 键值重复率低的字段比较适合使用B树索引. 位图索引键值重复率高的字段比较适合使用位图索引.count.and.or.in这些特定的 ...

  8. 【转】APUE学习1:迈出第一步,编译myls.c

    原文网址:http://blog.csdn.net/sddzycnqjn/article/details/7252444 注:以下写作风格均学习自潘云登前辈 /******************** ...

  9. 去除 waring Method 'CreateNew' hides virtual method of base type 'TCustomForm'

    最近整理前人的代码,有好多的hint和waring, 其中整理到Method 'CreateNew' hides virtual method of base type 'TCustomForm', ...

  10. HDU 5783 Divide the Sequence

    Divide the Sequence Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...