• 管理
  • 对清北学堂寒假精英班内容的整理

    Qbxt 寒假精英班 1.28

    例题1 全排列

    按照字典序从小到大枚举长度为N的全排列。

    从后往前找到第一个非增的元素。

    再从后往前找第一个比它大的元素。

    交换两个元素。

    反转后缀 。

    时间复杂度\(_{max} = O(N)\)(一次操作)。 均摊\(O(N !)\)


    例题2 激光炸弹

    二位前缀和优化。

    for (int i = 1 ; i <= M ; i ++)
    	Sum[X[i]][Y[i]] = V[i] ;
    for (int i = 1 ; i <= N ; i ++)
    for (int j = 1 ; j <= N ; j ++)
    	Sum[i][j] += Sum[i - 1][j] + Sum[i][j - 1] - Sum[i - 1][j - 1] ;
    

    例题3 Mines for Diamonds

    (UVa10606)

    \(N \times M\)矩阵中有若干钻石,要求找到若干条道路使得从规定边界开始覆盖掉所有的钻石,最小化道路方格。

    钻石数 \(≤ 10\)

    1. Bfs钻石间和钻石边界的曼哈顿距离。

    2. 枚举钻石排列。

    对于每一个钻石,按照枚举的排列,可以选择向后连接下一个钻石,也可以选择成为链终点。贪心取min即可。

    1. 贪心扫一遍。

    技巧

    1. 减少枚举状态。
    2. break优化:当得到答案时停止枚举。
    3. 卡时:在将要超时的时候停止枚举,直接输出当前最优解。(以一秒为例)
    while ((double)clock() / CLOCKS_PER_SEC < 1) work() ;
    

    迭代加深搜索

    
    void IdDfs(int Now, int D) {
    	if (D < 0) return ; Work(Now) ;
    	for (Now, To) in Edges
    		Dfs(Now, D - 1) ;
    }
    for (int D = 1 ; d <= Max_DEPTH ; D ++) IdDfs(Root, D) ;
    

    适用于需要寻找最优解,但是空间不够\(Bfs\)的情况。

    限定范围,然后寻找答案,如果没有答案,就扩大范围继续搜索。


    例题4 骑士精神

    1. \(Iddfs\)
    2. 最优性剪枝:当前局面与目标局面相差棋子数 \(≥\)剩余步数$ + 1$时剪枝。

    或者直接\(A^{*}\)


    \(A^*\)算法

    启发式算法。,用于寻找\(S -> T\)的最短路。设\(H[Now]\)为一个估价函数,\(G[Now]\)表示从起点\(S\)走到\(Now\)的最小代价。且定义\(F[Now] = G[Now] + G[Now]\)

    \(A^*\)中,\(H[Now]\)必须小于等于\(Now\)到终点的真实最短路:正确性。

    反之,若所有的\(H[Now]\)都小于等于\(Now\)\(T\)的真实最短路,那么我们一定可以找到最短路。

    //Q 为一个小根堆。
    G[S] = 0 ; F[S] = H[S] ; Q.push(S) ;
    while (! Q.empty() && Q.front() != T){
    	int Now = Q.front() ; Q.pop() ;
    	Vis[Now] = true ;
    	for (Now, To) in Edges
    		if (! Vis[To] && G[Now] + Dis(Now, V) < G[To]) {
    			G[To] = G[Now] + Dis(Now, V) ;
    			F[To] = G[To] + H[To] ;
    			Q.push(To) ;
    		}
    

    可以看到如果有\(H == 0\),那么上示代码就是个\(Dij\)

    当然还有迭代加深版的\(IDA^*\):限制最大的\(F\)函数。

    if (G + H[Now] > MaxF) return G + H[Now] ;
    

    以及函数外的

    int MaxF = H[S] ;
    while (MaxF != FOUND) MaxF = IDA(S, 0, MaxF) ;
    

    循环中:

    T = IDA(To , G + Dis[Now, V], MaxF) ;
    if (T == FOUND) return FOUND ;
    CHKMIN(Min, T) ;
    

    例题5 序列

    (Bzoj 5449)

    给定一个\(1 .. N\)的排列\(P\),每可以将\(P\)的某一个前缀反转,求出将序列变为升序的最小操作次数。

    估价函数设计为当前相邻两个数字绝对值大于1的个数,因为一次翻转最多消除一个这样的位置,所以最终答案肯定大于这个值。然后迭代加深搜索,用估价剪枝就可以了。


    分治

    分治的本质是分支的本质缩小问题规模。

    例如:定义点对\((X, Y)\)的权值为\(F(X, Y)\),要求统计\(\sum_{X }\sum_{Y} (F(X, Y) == S) ? 1 : 0\)

    分治的过程一定只能和自己的区间的长度相关,基本可以化\(N\)\(logN\)这样子。


    例题6 简单题

    给定一个数列A,和两个参数\(QL, QR\),求出满足\(QL < a_xa_y < QR(X < Y)\)的点对个数。

    对于分治区间\([L, R)\), 首先递归到\([L, Mid),[Mid, R)\),然后将两个区间内的数分别排序。

    利用\(Two~Pointers\)\(i\)指向\(A_1\)\(j\)指向\(B_N\),且\(A, B\)均递增。然后每当\(i\)移动时,为了满足\(a_X + B_Y < C\),当\(i\)向右移动时,\(j\)只能向左移动。

    B[0] = - Inf ;
    for (int i = 1, j = N ; i <= N ; i ++){
    	while (a[i] + B[i] >= Bound) j -- ;
    	Total += j ;
    }
    

    加上排序,总复杂度是\(O(Nlog^2N)\)。将排序改为归并排序可以将时间复杂度优化到\(O(NlogN)\)


    例题7 区间的价值

    定义\((L, R)\)区间价值为区间最大值\(\times\)区间最小值。求最大区间价值。\(N≤10^5\)。数据随机。

    最有区间问题我们仍然考虑分治,但是取中点显然很麻烦。

    min, max都在左边

    min, max都在右边

    min在左,max在右

    min在右,max在左

    显然很不好更新。

    由于数据随机,我们仍然可以考虑其他的点依然可以保证复杂度,比如我们考虑区间中最小的点最为分割点。

    区间扩大时,max可能会扩大,min可能会缩小。当答案变小仅当最小值变小。

    当区间分为了\((L, Pos\_Min),(Pos\_Min, R)\),其中\(Pos\_Max\)属于右区间。那么所有满足\(X < Pos\_Min \&\& Y > Pos\_Max\)的区间\((X, Y)\)权值都是一样的。

    只需要更新长度为\(|Pos\_Max - Pos\_min|\)\(|R - Pos\_Max|\)。然后取后缀最大值即可。


    例题8 非常规

    定义一个数列时合法的,当且仅当这个数列的每一个子串(连续子序列)。都只存在一个只出现过一次的元素。判断给定序列是否合法。

    暴力线段树......

    当然我们这里考虑分治。预处理\(Prev\)\(Next\),维护上一个\(A[i]\)出现的位置和下一个。然后我们就能快速判断\(A[i]\)是不是在区间\((L, R)\)中只出现了一次。(检查\(Prev[i],Next[i]\)是不是属于\(L, R\)就可以了)然后\(Check(L, R)\),检查区间\((L,R)\)是否合法。

    找到一个在此区间中只出现过一次的元素\(P\),然后递归\(Check(L, P)\)\(Check(P, R)​\)就可以了 。

    对于区间\(L,R\),从\(L,R\)同时出发寻找合法的\(P\)点。那么区间就被分成了两块,复杂度就正确了。


    可以看到分治实际上就是启发式合并的逆过程...


    例题9 稻草人

    给出N个点的坐标,定义一块矩阵合法,当且仅当其满足下列条件时:

    1. 矩阵时平行于坐标轴的长方形。
    2. 左下角和右上角各有一个点。
    3. 矩阵内部不包含点。

    求出合法矩阵的个数。

    将所有点按照纵坐标排序,然后按纵坐标分治。

    通过分治求出上下两块的答案,只需考虑左下角在下半部分,右上角在上半部分的答案。

    左下角\((X_1, Y_1)\),右上角\((X_2, Y_2)\)

    对于上半部分,对于所有横坐标在\([X_1, X_2]\)内的点要求纵坐标\(≥Y_2\)

    对于下半部分,对于所有横坐标在\([X_1, X_2]\)内的点要求纵坐标\(\leq Y_1\)

    枚举右上角,维护两个单调栈,在单调栈上二分擦护照符合条件的左下角。


    其他分治:

    树分治,CDQ分治,整体二分......

    总之\(Noip\)打死都不会考就是了,省选还是有可能的。


    1.28测试

    T1 逗猫

    现在你有一个\(01\)矩阵,你可以进行若干次操作,每一次操作可以将矩阵中的某一个元素换掉。现在要求你进行若干次操作后,使得矩阵满足下面两个条件,要要满足哪一个条件已经给出。

    1. 每一个元素的相邻上下左右元素中\(1\)的个数为奇数。
    2. 每一个元素的相邻上下左右元素中\(1\)的个数为偶数。

    要求最小化操作个数。

    对于\(30\%\)\(subtask\),枚举每一个元素是否更换然后检查。复杂度\(O(2^{NM}NM)\)

    对于\(N = 2\)的时候,按列考虑,发现对于某一个元素,更换不会影响前前一列和后后一列,就是说只会影响相邻两列的元素,当一列发生改变的时候,相邻两列改变,然后其它列改变...整个矩阵就被确定了,因此枚举第一列的元素怎么戴帽子,然后检查最后一列的需求是否满足即可。复杂度\(O(4NM)\)

    满分做法:由\(N = 2\)情况可推出将矩阵转置。复杂度\(O(2^{min(N, M)}NM)\)

    #include <bits/stdc++.h>
    const int MAXN = 305;
    int n, m, c;
    int a[MAXN][MAXN];
    int main() {
        scanf("%d%d%d", &n, &m, &c);
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                scanf("%d", n > m ? &a[i][j] : &a[j][i]);
        if (n <= m)
            std::swap(n, m);
    
        int ans = INT_MAX;
        for (int s = 0; s < (1 << m); ++s) {
            int b[MAXN][MAXN];
            for (int j = 0; j < m; ++j)
                b[0][j] = s >> j & 1;
            for (int i = 1; i < n; ++i)
                for (int j = 0; j < m; ++j) {
                    int left = j > 0 ? b[i - 1][j - 1] : 0;
                    int up = i > 1 ? b[i - 2][j] : 0;
                    int right = j < m - 1 ? b[i - 1][j + 1] : 0;
                    b[i][j] = left ^ up ^ right ^ c;
                }
            bool valid = true;
            for (int j = 0; j < m; ++j) {
                int left = j > 0 ? b[n - 1][j - 1]: 0;
                int up = n > 1 ? b[n - 2][j] : 0;
                int right = j < m - 1 ? b[n - 1][j + 1] : 0;
                valid &= left ^ up ^ right ^ c ^ 1;
            }
            if (valid) {
                int cur = 0;
                for (int i = 0; i < n; ++i)
                    for (int j = 0; j < m; ++j)
                        cur += a[i][j] ^ b[i][j];
                if (cur < ans)
                    ans = cur;
            }
        }
        printf("%d\n", ans == INT_MAX ? -1 : ans);
    }
    

    T2 换猫

    现在有一个序列,每次会有操作交换某两个元素,每次输出序列逆序对的奇偶性

    对于\(20\%\)\(subtask\),可以直接\(O(N^2)\)枚举所有的数对。时间复杂度\(O(N^2q)\).

    对于\(40 \%\)\(subtask\),k可以利用树状数组或者是归并排序求逆序对,时间复杂度\(O(NqlogN)\)

    对于\(70 \%\)\(subtask\),观察发现每次交换\(X,Y\)的位置,那么逆序对的奇偶性一定改变,于是对原序列求逆序对的奇偶性,每次操作时,如果\(X ≠ Y\),那么将上一次的答案取反。

    满分做法,由于我们在预处理逆序对的时候只需要知道它的奇偶性,所以可以有更快的方法。因为我们知道\(1, 2,3...N\)的逆序对个数是0,那么我们就只需要求出原始序列是由多少布从\(1,2,3...N\)变换过来的。

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 1e7 + 5;
    int n, q;
    int a[MAXN];
    int seed;
    int A = 20000527;
    int B = 20000909;
    int C = 20171210;
    int randint(int x) {
        seed = (1LL * seed * A + C) % B;
        return (seed % x) + 1;
    }
    
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            scanf("%d%d%d", &n, &q, &seed);
            for (int i = 1; i <= n; ++i)
                a[i] = i;
            for (int i = 2; i <= n; ++i)
                swap(a[i], a[randint(i - 1)]);
            int c = 0;
            for (int i = 1; i <= n; ++i) {
                if (!a[i])
                    continue;
                ++c;
                int j = a[i]; a[i] = 0;
                while (j) {
                    int t = a[j];
                    a[j] = 0;
                    j = t;
                }
            }
            int cur = (n - c) & 1;
            unsigned ans = 0;
            for (int i = 1; i <= q; ++i) {
                int x = randint(n), y = randint(n);
                cur ^= (x != y);
                ans = ans * 3 + cur;
            }
            printf("%u\n", ans);
        }
    }
    
    

    T3 运猫

    对于\(n = 3\) 的情况,只需要分类讨论三个节点的关系即可。复杂度\(O(1)\)

    对于\(n = c\) 的情况,最终情况每个节点恰有一只猫。预处理出每个节点之间的运输代价后,我
    们可以枚举每只猫分别从哪个节点运输到了哪个,再判断是否合法即可。
    复杂度\(O(n^n + n^2)\)

    接下来,在说明算法前,我们需要证明几个结论:

    1. 设把一只猫从\(u\) 运到\(v\) 的代价是\(dis(u, v)\) ,那么\(max(dis(u;w); dis(w; v)) ≥ dis(u; v).\)
      这个结论是显然的,因为所有从\(u\)\(v\) 的道路包含了所有从\(u\)\(w\) 再到\(v\) 的道路。
    2. 设一个点上猫多于平均值的为盈余点,猫少于平均值的为亏损点,那么猫的运输只会由盈余
      点运向亏损点。
      假设猫由一个盈余点\(u\) 运输到了盈余点\(v\)。由于\(v\) 节点上的猫最终只有平均值个,因此\(v\)
      必然还会向外输出猫,设其运输到了\(w\)。由上一条结论,直接从\(u\)\(w\) 运输猫必然不会比从\(u\)
      \(v\) 再运到\(w\) 劣,所以不需要把猫从\(u\) 运输到\(v\)
      同理,亏损点之间的运输也是无必要的。
      于是我们的问题变成了:一张二分图,左边向右边运输,不同的连边有不同的代价,要求每个
      左边的点运输完特定的猫的数量同时最小化代价。这个问题可以直接使用最小费用最大流解决。
    以上是50分的做法。

    Pic1

    Pic2

    Pic3

    #include <bits/stdc++.h>
    
    const int MAXN = 1e5 + 5, MAXM = 5e5 + 5;
    
    struct Edge {
        int u, v;
        int w;
        bool operator< (const Edge &rhs) const {
            return w < rhs.w;
        }
    };
    
    int n, m;
    int a[MAXN];
    Edge e[MAXM];
    int fa[MAXN];
    int size[MAXN];
    long long sum[MAXN];
    
    int getFather(int u) {
        return fa[u] == u ? u : fa[u] = getFather(fa[u]);
    }
    
    int main() {
        freopen("transfer.in", "r", stdin);
        freopen("transfer.out", "w", stdout);
    
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        for (int i = 1; i <= m; ++i)
            scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    
        int cnt = 0;
        long long k = 0, ans = 0;
    
        std::sort(e + 1, e + m + 1);
        for (int i = 1; i <= n; ++i) {
            fa[i] = i;
            size[i] = 1;
            k += sum[i] = a[i];
        }
        assert(k % n == 0);
        k /= n;
        for (int i = 1; i <= m; ++i) {
            int u = e[i].u, v = e[i].v, w = e[i].w;
            int fu = getFather(u), fv = getFather(v);
            if (fu == fv)
                continue;
            long long du = sum[fu] - k * size[fu];
            long long dv = sum[fv] - k * size[fv];
            if (du > 0 && dv < 0)
                ans += std::min(du, -dv) * w;
            else if (du < 0 && dv > 0)
                ans += std::min(-du, dv) * w;
            ++cnt;
            fa[fu] = fv;
            size[fv] += size[fu];
            sum[fv] += sum[fu];
        }
        assert(cnt == n - 1);
    
        printf("%lld\n", ans);
    }
    
    

    例题9 Final Bazarek

    (Bzoj 3721)

    给定N个数,多次询问选择K个数使得和为奇数的最大和。其中\(N \leq 1e6,询问个数 \leq 1e6\)

    从大到小排序,选出前K个,然后从前K个数中选出最小的一个奇数换成后\(N - K\)个数中最大的偶数,反之亦然。

    所以算法就是预处理出后\(i\)位的最大奇数或者偶数,前\(i\)位数最小的奇数和偶数,然后将两种方案比较一下就可以了。


    例题10 sam-Toy Cars

    (Bzoj 1528)

    目前有\(N\)个玩具,都放在架子上,地板上不能存在超过\(K\)个玩具,不在地板上的就要到架子上拿,二地板满了要放回去,要求最小化操作次数。

    贪心,每次选择下次玩的石间最靠后的玩具放回去。用大根堆维护一下玩具下一次玩的时间。


    例题11 Bohater

    (Bzoj3709)

    现在有N个怪物,打败怪物需要消耗对应的D[i]的生命值,但怪物死掉之后会掉落血药,恢复A[i]的生命值。给定初始的生命值不能死亡,求打怪顺序。

    把所有的怪分为两类:最终可以回血\((A_i > D_i)\),最终会扣血\((A_i \leq D_i)\)

    考虑一下情况:总血量102,D[1] = 100,A[1] = 0, D[2] = 2, A[2] = 1。那么按照顺序结果显然是不一样的。那么可以知道应该按照\(A[i]\)从大到小排序。

    对于可以回血的怪,按照\(D[i]\)从小到大排序。对于不可以回血的怪,按照\(A[i]\)从大到小排序,如果存在某一个怪打不了,则无解。


    最大公约数

    欧几里得算法:\(gcd(A, B) = gcd(B, A ~ mod~ B)\)

    inline int Gcd(int X, int Y) {
    	if (! Y) return X ;
    	return Gcd(Y, X % Y) ; 
    }
    

    最小公倍数

    \(lcm(X, Y) = \frac{X \times Y}{gcd(X, Y)}\)

    推广\(lcm(X, Y, Z)\)可以得到\(lcm(S) = \prod_{T \in S} gcd(T)^{(-1)^{|T |+ 1}}\),其中\(S\)为元素集合,T为子集。

    子集反演

    \(max{S} = \sum_{T \in S} (-1)^{|T| + 1} min(T)\)

    扩展欧几里得

    给定\(A, B\),求一组\(X, Y\)满足\(AX + BY = gcd(A, B)\)

    若已知\(X_0, Y_0\)满足\(BX_0 + (A ~mod~B)Y_0 = gcd(B, A ~mod~ B)\)

    \(BX_0 + (A - \lfloor\frac{A}{B} \rfloor \times B)Y_0 = gcd(B, A ~mod~ B)\)

    inline int Exgcd(int A, int B, int X, int Y) {
    	if (! Y) {
    		X = 1, Y = 0 ; return A ;
    	}
    	int D = Exgcd(B, A % B, Y, X) ;
    	Y -= X * (A / B) ;
    	return D ;
    }
    

    \(gcd\)\(lcm\)的性质

    \(lcm(S) = \prod_{T \in S} gcd(T)^{(-1)^{|T |+ 1}}\)

    \(gcd(Fib(a), Fob(b)) = Fib(gcd(a, b))\)

    \(gcd(X^a - ,X_b - 1) = X^{gcd(a, b)} - 1\)


    例题12 Bzoj4833

    已知\(F(N) = 2F(N - 1) + F(N - 2)\),且\(F(0) = 0, F(1) = 1\)。定义\(G(N) = lcm(F(1), F(2)...F(N))\)。求\(\sum_{i = 1}^N G(i) * i ~(mod~p)\)的值,其中\(p\)是一个质数。

    可以推出\(F\)函数满足性质2:\(gcd(F(a), F(b)) = F(gcd(a, b))\)\(G(N) = \prod_{T \in 2^N} F(gcd(i))^{-1^{|T| + 1}}\), \(F(N) = \prod_{(D|N)}H(D)\).利用莫比乌斯反演求出\(H()\)

    得到\(G(N) = \prod_{T \in 2^N}(\prod_{D|gcd_{i \in T^{(i)}}}H(d))^{(-1)^{|T| + 1}}\)最后得到\(G(N) = \prod_{D = 1}^N H(D)\)


    整除分块

    考虑当\(i = 1...N\)时,\(\lfloor \frac{N}{i} \rfloor\)的取值情况。

    for (int i = 1, Last ; i <= N ; i = Last + 1) {
    	int A = N / i ;
    	Last = N / A ;
    	Ret += F(A) * (Sum(last) - Sum(i - 1)) ;
    	//Sum(i)快速求前缀和。
    }
    

    例题12 Hdu 5780

    \(\sum_{1 \leq a, b \leq N}gcd(X^a - 1, X^b - 1)\)\(X, N \leq 100000\), 300组数据。

    根据性质3,式子可以转化为\(\sum_{1 \leq a,b \leq N}X^{gcd(a,b) } - 1\)

    \(Ans = \sum_{K = 1}^N(X^K-1) \sum [gcd(a,b) =K]\)

    且有\(gcd(a,b) = k\) ->\(gcd(\frac ak,\frac bk) = 1\) ->\(gcd(a',b') = 1\) -> \(fai\)

    \(Ans = \sum_{K = 1}^N (X^k -1)(2 \sum_{i = 1}^{\lfloor \frac NK \rfloor} \varphi(i) - 1)\)

    可用整除分块。


    费马小定理

    \(a^{p - 1} \equiv 1 (mod~p)\),其中\(p\)是一个质数,且\(a\)不是\(p\)的倍数。

    欧拉定理

    \(a^{\varphi(p)} \equiv 1 (mod~p)\),其中\(a,p\)互质。

    可以看出,欧拉定理是费马小定理的推广。

    而在一般情况下:\(a^m \equiv a^{min(m, (m ~mod ~\varphi(n))+\varphi(N))} (mod~p)\)

    一下是利用费马小求乘法逆元。

    
    inline int Fpm(int X, int P) {
    	int Ans = 1 ; int M = P ;
    	X %= M ; P -= 2 ;
    	while (P) {
    		if (P & 1) Ans = Ans * X % M ;
    		A = A * A % M ; P >>= 1 ;	
    	}	return Ans % M ;
    }
    

    例题13 经典题

    \(2^{2^{2^{2^{2^{2^{2^{...}}}}}}} ~mod ~p\)的值。

    指数\(2^{🌫}\)必定是\(≥ \varphi(p)\)

    那么\(a^b≡a^{(b ~mod~ φ(p))+φ(p)}\)。然后直接递归就可以了。

    因为最多递归\(logp\)次,所以时间复杂度就是\(O(logp)\)


    中国的剩余定理

    求一元模线性方程组\(X \equiv a_i (mod~P_i)\)的一个通解,\(P_i\)两两互质。

    \(P' = \prod P_i\),和\(P'_i = \frac{P'}{P_i}\)

    \(T_i = P'^{-1}_i~ mod P_i\)

    构造通解:\(X \equiv \sum a_iT_iP'_i (mod~P')\)


    筛法

    埃拉托斯特尼筛法

    
    bool Prime[MAXN] ;
    inline void S() {
    	Prime[1] = false ;
    	for (int i = 2 ; i <= N ; i ++) Prime[i] = 1 ;
    	for (int i = 2 ; i <= sqrt(N) ; i ++)
    		if ()
    		for (int j = i * 2 ; i <= N ; j += i)
    			NotP[i * j] = false ;
    }
    

    时间复杂度\(O(NloglogN)\)

    欧拉筛法

    
    int P[MAXN] ; bool Prime[MAXN] ;
    inline void S() {
    	for(int i = 1 ; i <= N ; i ++) Prime[i] = 1 ;
    	for (int i = 2 ; i <= N ; i ++) {
    		if (Prime[i]) Prime[++ Tot] = i ;
    		for (int j = 0 ; j <= Tot && i * P[j] < N ; j ++) {
    			Prime[i * P[j]] = false ;
    			if (! i % P[j]) break ;
    		}
    	}
    }
    

    时间复杂度\(O(N)\),线性筛法。(其实也不是完全线性,很接近而已......)。

    posted @ 2019-01-29 11:22  Sue_Shallow  阅读(262)  评论(0)    收藏  举报
    Live2D