线段树模板及技巧分析

来源:互联网 发布:欠淘宝贷款50万 编辑:程序博客网 时间:2024/06/06 06:59

——————ver. 2016.3.13——————
———模板建设中,如有建议欢迎指出———

关键词:空间压缩、递归优化

源码下载地址:http://pan.baidu.com/s/1o6UZ7qM

本文分析了基于比较的线段树的优化技巧,并且附带了一份封装好的源码。
0、不要吐槽代码规范和多(bu)样(yi)化(zhi)的缩进风格%>_<%
1、不要问我考场上怎么写得了这么多,重点是技巧,技巧啦%>_<%
2、求和线段树会在以后给出,目前还不(lan)支(de)持(xie)

#ifndef FUNCTOR_H#define FUNCTOR_Htemplate <class ValueType>struct GetLess{    ValueType operator () (const ValueType& x,const ValueType& y)    {        return x<y?x:y;    }};template <class ValueType>struct Less {    bool operator () (const ValueType& x,const ValueType& y) const     {        return x<y;    }};

我们把比较和选择函数封装成了仿函数(重载()运算符)的形式。
1、仿函数可以方便地用作模板类的模板参数。(虽然理论上函数指针也可以,不过一般没人这么做)
以我们熟悉的STL的priority_queue为例。定义其对象:
std::priority_queue < int,std::vector< int >,std::greater< int > >
我们定义了一个int类型的小根堆。这三个模板参数,第一个指定数据的类型是int,第二个指定优先队列内部使用的容器是std::vector,而第三个就是< functional >头文件中提供的仿函数,用于数据之间的比较。

#include <functional>int A,B;std::greater<int> cmp;bool flag=cmp(A,B);/*————————————————*/bool greater(int x,int y) {return x>y;}bool flag=greater(A,B);

仿函数的使用方法如上:定义一个仿函数对象,然后把它当作函数一样调用。cmp仿函数的功能和greater函数是等价的。

2、仿函数一般比函数指针要快。不过要避免每次使用仿函数都建立一个临时对象,否则会拉低效率。

template <class ValueType>struct SegNode_Cmp_Primary {    ValueType val;    inline void assign(const ValueType& _val) {        val = _val;    }    inline void add(const ValueType& _val) {        val = val + _val;    }    inline void set(const ValueType& _val) {        val = _val;    }    inline ValueType actualVal() {        return val;    }};template <class ValueType>struct SegNode_Cmp_Secondary : public SegNode_Cmp_Primary<ValueType> {    typedef SegNode_Cmp_Primary<ValueType> BaseI;    using BaseI::val;    ValueType lazyTag;    inline void assign(const ValueType& _val) {        val = _val;        lazyTag = ValueType();    }    inline void add(const ValueType& _val) {        lazyTag = lazyTag + _val;    }    inline void set(const ValueType& _val) {        val = _val;        lazyTag=ValueType();    }    inline void reset() {        val = val + lazyTag;        lazyTag = ValueType();    }    inline ValueType actualVal() {        return val + lazyTag;    }};template <class ValueType>struct SegNode_Cmp_Tertiary : public SegNode_Cmp_Secondary<ValueType> {    typedef SegNode_Cmp_Primary<ValueType> BaseI;    typedef SegNode_Cmp_Secondary<ValueType> BaseII;    using BaseI::val;    using BaseII::lazyTag;    char status;    inline void assign(const ValueType& _val) {        val = _val;        lazyTag = ValueType();        status='n';    }    inline void add(const ValueType& _val) {        lazyTag = lazyTag + _val;        if (status == 'n') status = 'a';    }    inline void set(const ValueType& _val) {        lazyTag = _val;        status = 's';    }    inline void reset() {        switch (status) {            case 'n':                break;            case 'a':                val = val + lazyTag;                lazyTag = ValueType();                status = 'n';                break;            case 's':                val = lazyTag;                lazyTag = ValueType();                status = 'n';                break;        }    }    inline ValueType actualVal() {        switch (status) {            case 'n':                return val;            case 'a':                return val + lazyTag;            case 's':                return lazyTag;        }    }};

线段树的节点。
我们以类继承的方式将节点分为了三级:
level primary:支持区间询问和单点修改(add或set),但不允许区间修改操作。
这种情况下,我们实际上不需要lazyTag,所以在一级节点中只有val一个成员变量。
level secondary:支持区间询问和单点修改,允许区间add但不允许区间set操作。
在这一级中设立了lazyTag,用来延迟修改从而提高效率。
level tertiary:支持区间询问及修改(add和set)
status变量的作用是表明lazyTag属于何种类型:
‘n’:没有lazyTag
‘a’:lazyTag的作用是区间add
‘s’:lazyTag的作用是区间set
值得注意的是lazyTag的覆盖或叠加(上层‘s’标记覆盖下层标记,或是上层’a‘标记与下层标记叠加)。详见代码。

(貌似类的成员函数默认是内联的,所以这里的inline关键字加不加都无所谓)

#ifndef SEGMENTTREE_H#define SEGMENTTREE_H#include "SegmentTree/SegNode.h"#include "ErrorAndMistake.h"#include <cstdlib>#include <cstdio>struct PrimaryTag {};struct SecondaryTag : public PrimaryTag {};struct TertiaryTag : public SecondaryTag {};template <class ValueType,class Tag>struct NodeTraits {    typedef SegNode_Cmp_Primary<ValueType> SegNode;};template <class ValueType>struct NodeTraits<ValueType, SecondaryTag> {    typedef SegNode_Cmp_Secondary<ValueType> SegNode;};template <class ValueType>struct NodeTraits<ValueType, TertiaryTag> {    typedef SegNode_Cmp_Tertiary<ValueType> SegNode;};#define SEGTREE_CMP_TEMPLATE template \    <class ValueType,class OperationFunctor,class SegNodeTag>#define SEGTREE_CMP_TYPE SegmentTree_Cmp \    <ValueType,OperationFunctor,SegNodeTag>template <class ValueType,class OperationFunctor,class SegNodeTag>class SegmentTree_Cmp {protected:    typedef typename NodeTraits<ValueType, SegNodeTag>::SegNode SegNode;    typedef SegNode* DynamicArray;    DynamicArray node;    int right;    int cpStack[32];    int rtStack[32];    int cur;    OperationFunctor func;    SegNodeTag tag;    inline int middle(int nodeL, int nodeR) {        return (nodeL + nodeR) >> 1;    }    inline int rightIndex(int curIdx, int nodeL, int mid) {        return curIdx + ((mid + 1 - nodeL) << 1);    }    void init_aux(const ValueType& _val);    template <class RAIterator>    void init_aux(RAIterator first, int curIdx, int len);    void dropLazyTag(int curIdx, int rightIdx, SecondaryTag);    void dropLazyTag(int curIdx, int rightIdx, TertiaryTag);    void rangeAdd_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR,            int nodeL, int nodeR, int stackPos, PrimaryTag);    //just throw an error    void rangeAdd_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR,            int nodeL, int nodeR, int stackPos, SecondaryTag);    void rangeSet_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR,            int nodeL, int nodeR, int stackPos, PrimaryTag);    void rangeSet_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR,            int nodeL, int nodeR, int stackPos, TertiaryTag);    void pointSet_aux(const ValueType& _val, int curIdx, int pos, int nodeL, int nodeR, PrimaryTag);    void pointSet_aux(const ValueType& _val, int curIdx, int pos, int nodeL, int nodeR, SecondaryTag);    template <class Comparator>    bool pointReplace_aux(const ValueType& _val, int curIdx, int pos,            int nodeL, int nodeR, Comparator cmp, PrimaryTag);    template <class Comparator>    bool pointReplace_aux(const ValueType& _val, int curIdx, int pos,            int nodeL, int nodeR, Comparator cmp, SecondaryTag);    ValueType rangeAsk_aux(int curIdx, int rangeL, int rangeR, int nodeL, int nodeR, PrimaryTag);    ValueType rangeAsk_aux(int curIdx, int rangeL, int rangeR, int nodeL, int nodeR, SecondaryTag);public:    SegmentTree_Cmp(int _len) : right(_len - 1) {        node = new SegNode[_len << 1];        cur = -1;    }    ~SegmentTree_Cmp() {        delete[] node;    }    void init(const ValueType& _val) {        init_aux(_val);    }    template <class RAIterator>    void init(RAIterator first) {        init_aux(first, 0, right + 1);    }    void rangeAdd(const ValueType& _val, int rangeL, int rangeR) {        try {            rangeAdd_aux(_val, 0, rangeL, rangeR, 0, right, -1, tag);        }        catch (IllegalOperation) {            printf("\nNot allowed to call rangeAdd() function with a single-modifiable segment tree!\n");            exit(1);        }    }    void rangeSet(const ValueType& _val,int rangeL,int rangeR) {        try {            rangeSet_aux(_val,0,rangeL,rangeR,0,right,-1,tag);        }        catch(IllegalOperation) {            printf("\nNot allowed to call rangeSet() function with a non-range-settable segment tree!\n");            exit(1);        }    }    void pointSet(const ValueType& _val, int pos) {        pointSet_aux(_val, 0, pos, 0, right, tag);    }    template <class Comparator>    bool pointReplace(const ValueType& _val, int pos, Comparator cmp) {        return pointReplace_aux(_val, 0, pos, 0, right, cmp, tag);    }    ValueType rangeAsk(int rangeL, int rangeR) {        return rangeAsk_aux(0, rangeL, rangeR, 0, right, tag);    }    ValueType askAll() {        return node[0].actualVal();    }};#include "SegmentTree/Cmp_Details/Init_aux.h"#include "SegmentTree/Cmp_Details/DropLazyTag.h"#include "SegmentTree/Cmp_Details/RangeAdd_aux.h"#include "SegmentTree/Cmp_Details/RangeAsk_aux.h"#include "SegmentTree/Cmp_Details/PointSet_aux.h"#include "SegmentTree/Cmp_Details/PointReplace_aux.h"#include "SegmentTree/Cmp_Details/RangeSet_aux.h"#endif /* SEGMENTTREE_H */

(请允许我吐槽一下NetBeans蛋疼的AStyle⊙﹏⊙b)
首先是文件开头的3个tag(空的struct)。其作用是作为线段树的模板参数,标明你使用的哪一级的节点。同样,我们使用了类继承的形式。
与之对应的是下面的3个traits。它们的作用就是利用typedef将你需要的节点类型“萃取”出来。两个特化版本分别针对secondary和tertiary节点。如果你选的是PrimaryTag,那就按第一个非特化版本来。

之后就是SegmentTree_Cmp类的成员声明或定义。
首先看成员变量:
第一个typedef配合上面的NodeTraits共同完成对节点的选择。
第二个……咳咳……

node数组:不言而喻。
right:表示线段树的区间总范围是[0,right]。
func:仿函数对象。前面说了用一次就设一个临时变量的话会浪费时间。
tag:对应前面的3个tag结构体。由于节点类型的不同,同一操作需要不同的策略,tag的作用就是视情况“分流”。设立一个对象的原因同func。

cpStack和rtStack的性质顾名思义,cur则是这两个栈共用的栈顶指针。
其作用是进行手动递归优化,具体原理稍后再讲。

然后看成员函数:
mid:求区间节点[nodeL,nodeR]的中点。我们将区间分成了[nodeL,mid]和[mid+1,nodeR]两个部分。

rightIdx:看起来比较诡异⊙﹏⊙b
这涉及到了我们如何存储和组织线段树节点
引理:若某线段树节点的区间长度为L(即nodeR-nodeL+1),则以该节点为根的子树的节点数为f(L)=2L-1.
如果L为偶数,则f(L)=2f(L/2)+1;
如果L为奇数,记a=(L-1)/2,b=(L+1)/2,则f(L)=f(a)+f(b)+1
递推起点f(1)=1
之后由数学归纳法易证。
由该引理可知:我们需要的空间实际上只有总区间长度的两倍。
我们把根节点标号为0,然后进行先序遍历(根->左->右)。
记当前节点的标号为x,则:
左孩子的标号为x+1
右孩子的标号为x+2L(L为左孩子的区间长度)
x+2L实际上就是x+(2L-1)+1,表示遍历完左子树的2L+1个节点后,下一个节点就是右孩子。
节点本身并没有存储这些信息,它们都是即时计算出来的。
诡异的是:我原本想把每个节点的mid和rightIdx存储到node结构体中,从而避免大量的即时计算。然而预处理后的效率远远低于即时计算!
(哪位大牛知道原因可否指点一下(⊙o⊙) )

之后的一大堆aux(=auxiliary)函数对应相应接口(即public函数)的底层操作。而这里tag的作用就凸显出来了:

    void dropLazyTag(int curIdx, int rightIdx, SecondaryTag);    void dropLazyTag(int curIdx, int rightIdx, TertiaryTag);

以这两个为例,我们给dropLazyTag函数重载了两个版本,分别对应secondary节点和tertiary节点的lazyTag处理策略。tag的作用,就是在调用时确定该使用哪个版本。
最后一个函数参数并没有给出变量名,因为不需要:-D

接口函数及功能如下:
init:将线段树初始化。用某个数值或数组填充线段树。
rangeAsk:区间询问。
rangeAdd:给区间赋予一个增量_val。
rangeSet:将区间所有值设为_val。
pointSet:将某个点的值设为_val。
pointReplace:传递一个仿函数对象(或函数指针)cmp,当cmp(_val,该位置的值)为true时,等效于pointSet,否则,不做修改。

#ifndef INIT_AUX_H#define INIT_AUX_HSEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::init_aux(const ValueType& _val) {    int s = (right + 1) << 1;    for (int i = 0; i < s; ++i) node[i].assign(_val);}SEGTREE_CMP_TEMPLATEtemplate <class RAIterator>void SEGTREE_CMP_TYPE::init_aux(RAIterator first, int curIdx, int len) {    if (len == 1)        node[curIdx].assign(*first);    else {        int mid = (len + 1) >> 1;        int rightIdx = curIdx + (mid << 1);        init_aux(first, curIdx + 1, mid);        init_aux(first + (mid), rightIdx, len >> 1);        node[curIdx].assign(                func(node[curIdx + 1].val, node[rightIdx].val));    }}#endif /* INIT_AUX_H */

第一个版本很简单,直接赋值。
第二个版本利用了一个Random Access Iterator。
所谓Random Access Iterator(随机存取迭代器),就是支持+,-,*(取值)运算的迭代器。C语言的指针(它们被称作原生指针,顺带一提,数组名是常量指针)和std::vector的迭代器都属于这一类。

#ifndef DROPLAZYTAG_H#define DROPLAZYTAG_HSEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::dropLazyTag (int curIdx,int rightIdx, SecondaryTag){    node[curIdx+1].add(node[curIdx].lazyTag);    node[rightIdx].add(node[curIdx].lazyTag);    node[curIdx].reset();}SEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::dropLazyTag (int curIdx,int rightIdx, TertiaryTag){    switch (node[curIdx].status)    {    case 'n':        return;    case 'a':        node[curIdx + 1].add (node[curIdx].lazyTag);        node[rightIdx].add (node[curIdx].lazyTag);        node[curIdx].reset ();        break;    case 's':        node[curIdx + 1].set (node[curIdx].lazyTag);        node[rightIdx].set (node[curIdx].lazyTag);        node[curIdx].reset ();        break;    }}#endif /* DROPLAZYTAG_H */

向下传递lazyTag的操作。
对于secondary节点,直接给孩子节点加上当前节点的lazyTag值即可,然后将当前节点的lazyTag重设。
对于tertiary节点,
则需要根据当前节点的lazyTag类型,选择不同的下传方式

SEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::rangeAdd_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR, int nodeL, int nodeR, int stackPos, SecondaryTag) {    while (1) {        if (rangeL == nodeL && rangeR == nodeR) {            node[curIdx].add(_val);            break;        } else {            int MID = middle(nodeL, nodeR);            int RIGHT = rightIndex(curIdx, nodeL, MID);            dropLazyTag(curIdx, RIGHT, tag);            if (rangeR <= MID) {                cpStack[++cur]=curIdx;                rtStack[cur]=RIGHT;                ++curIdx;                nodeR=MID;            } else if (rangeL > MID) {                cpStack[++cur]=curIdx;                rtStack[cur]=RIGHT;                curIdx=RIGHT;                nodeL=MID+1;            } else {                rangeAdd_aux(_val, curIdx + 1, rangeL, MID, nodeL, MID,cur, tag);                cpStack[++cur]=curIdx;                rtStack[cur]=RIGHT;                curIdx=RIGHT;                nodeL=rangeL=MID+1;            }        }    }    for(;cur>stackPos;--cur) {        int& _curIdx=cpStack[cur];        int& _rightIdx=rtStack[cur];        node[_curIdx].val=func(node[_curIdx+1].actualVal(),node[_rightIdx].actualVal());    }}SEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::rangeAdd_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR, int nodeL, int nodeR, int stackPos, PrimaryTag) {    throw IllegalOperation();}

区间add的常规写法是:向左递归,向右递归,或者是先后进行两者。在递归退栈的过程中更新节点的值。实际上很多递归的过程都是不必要的,尤其是对于单向下传的情况。
我们采取了递归优化的策略。利用两个栈:cpStack和rtStack,分别存放“递归”(因为不是真正的递归所以加了引号)过程中每一步的curIdx和rightIdx的值。
如果单向下传,则只需要使当前的curIdx和rightIdx入栈。
如果双向下传,递归其中的一支(代码中为递归左孩子)即可,另一支仍然利用单向下传的策略。
最后退栈到最初的位置(由函数的倒数第二个参数决定,递归的第一层这个参数为-1)。退栈过程中更新沿途的节点。我们直接给node的val成员赋值,是因为进栈的过程中lazyTag已经被更新掉了。

如果节点是primary的(不允许区间修改),那么我们什么都不要做,只抛出一个IllegalOperation()异常信号。

    void rangeAdd(const ValueType& _val, int rangeL, int rangeR) {        try {            rangeAdd_aux(_val, 0, rangeL, rangeR, 0, right, -1, tag);        }        catch (IllegalOperation) {            printf("\nNot allowed to call rangeAdd() function with a single-modifiable segment tree!\n");            exit(1);        }    }

这是对应的接口函数。如果接受到了异常信号,就输出错误提示并且强行退出程序(好狠O__O”…)

其他区间操作的递归优化策略类似。而对于单点操作,我们完全可以不用递归。

其他部分限于篇幅就不再贴出了,详见代码。事实上稍加观察就会发现很多操作都如出一辙,只是视实际情况有些差异。

最后附上“借教室”一题的测试结果:

测试数据 #0: Accepted, time = 0 ms, mem = 548 KiB, score = 5

测试数据 #1: Accepted, time = 0 ms, mem = 548 KiB, score = 5

测试数据 #2: Accepted, time = 0 ms, mem = 568 KiB, score = 5

测试数据 #3: Accepted, time = 0 ms, mem = 572 KiB, score = 5

测试数据 #4: Accepted, time = 0 ms, mem = 572 KiB, score = 5

测试数据 #5: Accepted, time = 0 ms, mem = 568 KiB, score = 5

测试数据 #6: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #7: Accepted, time = 15 ms, mem = 2508 KiB, score = 5

测试数据 #8: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #9: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #10: Accepted, time = 46 ms, mem = 2504 KiB, score = 5

测试数据 #11: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #12: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #13: Accepted, time = 78 ms, mem = 2508 KiB, score = 5

测试数据 #14: Accepted, time = 328 ms, mem = 20128 KiB, score = 5

测试数据 #15: Accepted, time = 500 ms, mem = 20124 KiB, score = 5

测试数据 #16: Accepted, time = 656 ms, mem = 20128 KiB, score = 5

测试数据 #17: Accepted, time = 781 ms, mem = 20124 KiB, score = 5

测试数据 #18: Accepted, time = 1093 ms, mem = 20128 KiB, score = 5

测试数据 #19: Accepted, time = 1109 ms, mem = 20124 KiB, score = 5

Accepted, time = 4761 ms, mem = 20128 KiB, score = 100

ps附一个完全不使用递归的rangeAdd_aux版本:
(以下的无递归版本和以上的半递归版本目前很难说孰优孰劣。本机上随机化测试时前者比后者慢出将近1s(debug模式,100万次rangeAdd操作,前者7.5s,后者6.5s),而在OJ上提交时后者总是比前者快o(╯□╰)o,不知为何)

//New things in SegmentTree_Cmp classint buf[32];    int nodeLBuf[32];    int nodeRBuf[32];    int rangeRBuf[32];    int bufPos;
//New constructorSegmentTree_Cmp(int _len) : right(_len - 1) {        node = new SegNode[_len << 1];        cur = -1;        bufPos=0;        buf[0]=-1;    }
//New rangeAdd_aux() functionSEGTREE_CMP_TEMPLATEvoid SEGTREE_CMP_TYPE::rangeAdd_aux(const ValueType& _val, int curIdx, int rangeL, int rangeR, int nodeL, int nodeR,SecondaryTag) {    do {        while (1) {            if (rangeL == nodeL && rangeR == nodeR) {                node[curIdx].add(_val);                break;            } else {                int MID = middle(nodeL, nodeR);                int RIGHT = rightIndex(curIdx, nodeL, MID);                dropLazyTag(curIdx, RIGHT, tag);                if (rangeR <= MID) {                    cpStack[++cur] = curIdx;                    rtStack[cur] = RIGHT;                    ++curIdx;                    nodeR = MID;                } else if (rangeL > MID) {                    cpStack[++cur] = curIdx;                    rtStack[cur] = RIGHT;                    curIdx = RIGHT;                    nodeL = MID + 1;                } else {                    buf[++bufPos] = cur;                    nodeLBuf[bufPos] = MID + 1;                    nodeRBuf[bufPos] = nodeR;                    rangeRBuf[bufPos] = rangeR;                    cpStack[++cur] = curIdx;                    rtStack[cur] = RIGHT;                    ++curIdx;                    rangeR = nodeR = MID;                }            }        }        if (bufPos) {            for (; cur > buf[bufPos]+1; --cur) {                int& _curIdx = cpStack[cur];                int& _rightIdx = rtStack[cur];                node[_curIdx].val = func(node[_curIdx + 1].actualVal(), node[_rightIdx].actualVal());            }            nodeL = rangeL = nodeLBuf[bufPos];            nodeR = nodeRBuf[bufPos];            rangeR = rangeRBuf[bufPos--];            curIdx = rtStack[cur];        }        else {            for (; cur > buf[bufPos]; --cur) {                int& _curIdx = cpStack[cur];                int& _rightIdx = rtStack[cur];                node[_curIdx].val = func(node[_curIdx + 1].actualVal(), node[_rightIdx].actualVal());            }        }    }    while (cur>-1);}

最好的评测结果(还是借教室)如下:(不过比较糟的时候也会达到4700ms左右)

测试数据 #0: Accepted, time = 0 ms, mem = 552 KiB, score = 5

测试数据 #1: Accepted, time = 0 ms, mem = 552 KiB, score = 5

测试数据 #2: Accepted, time = 0 ms, mem = 572 KiB, score = 5

测试数据 #3: Accepted, time = 0 ms, mem = 576 KiB, score = 5

测试数据 #4: Accepted, time = 0 ms, mem = 568 KiB, score = 5

测试数据 #5: Accepted, time = 0 ms, mem = 568 KiB, score = 5

测试数据 #6: Accepted, time = 15 ms, mem = 2508 KiB, score = 5

测试数据 #7: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #8: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #9: Accepted, time = 31 ms, mem = 2508 KiB, score = 5

测试数据 #10: Accepted, time = 15 ms, mem = 2508 KiB, score = 5

测试数据 #11: Accepted, time = 62 ms, mem = 2508 KiB, score = 5

测试数据 #12: Accepted, time = 78 ms, mem = 2508 KiB, score = 5

测试数据 #13: Accepted, time = 46 ms, mem = 2508 KiB, score = 5

测试数据 #14: Accepted, time = 265 ms, mem = 20128 KiB, score = 5

测试数据 #15: Accepted, time = 406 ms, mem = 20128 KiB, score = 5

测试数据 #16: Accepted, time = 593 ms, mem = 20128 KiB, score = 5

测试数据 #17: Accepted, time = 781 ms, mem = 20128 KiB, score = 5

测试数据 #18: Accepted, time = 906 ms, mem = 20128 KiB, score = 5

测试数据 #19: Accepted, time = 1015 ms, mem = 20124 KiB, score = 5

Accepted, time = 4275 ms, mem = 20128 KiB, score = 100

1 0
原创粉丝点击