zoukankan      html  css  js  c++  java
  • RecycleBin解析

    RecycleBin机制

    RecycleBinAbsListView 中的一个内部类,因而所有继承自AbsListView的子类,即ListViewGridView,都可以使用这个机制,这个机制保障了 ListView实现上千条数据都不好OOM的最重要的一个原因

    RecylceBin源码解析

    参数解析

    • RecyclerListener mRecyclerListener,RecyclerListener 中只有一个函数void onMovedToScrapHeap(View view),作用是指明某个 view 被回收到了 recycler's scrap heap,那么该视图不再显示在屏幕上,与该视图相关联的任何昂贵资源都应丢弃
    • int mFirstActivePosition ,第一个可见元素的 position 值
    • View[] mActiveViews ,直接复用的view(处于可见状态的view)
    • ArrayList[] mScrapViews,间接复用的view(处于不可见状态的view)。可以被适配器用作convert view 的无序 view 数组。特别指明:这里是一个数组,因为如果adapter中数据有多种类型,那么就会有多个ScrapViews。
    • int mViewTypeCount,View 类型总数
    • ArrayList mCurrentScrap,与 mScrapViews 类似,当 mViewTypeCount = 1情况下,mCurrentScrap = mScrapViews [0]

    下面三个参数分别对应addScrapView()方法中 scrapHasTransientState 的三个情况

    • SparseArray mTransientStateViews,If the data hasn't changed, we can reuse the views at their old positions.
    • LongSparseArray mTransientStateViewsById, If the adapter has stable IDs,we can reuse the view forthe same data.
    • ArrayList mSkippedScrap,Otherwise, we'll have to remove the view and start over.

    方法解析

    • void markChildrenDirty(),为每个子类调用 forceLayout()。将mScrapView中回收回来的View设置一样标志,在下次被复用到 ListView 中时,告诉 viewroot 重新 layout 该 view。forceLayout()方法只是设置标志,并不会通知其 parent 来重新 layout

    • boolean shouldRecycleViewType(int viewType),判断给定的 view 的 viewType 指明是否可以回收回。viewType < 0可以回收。指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),或者是 HeaderView / FootView(ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。如有特殊需要可以将自己定义的viewType设置为-1,否则,将会浪费内存,导致OOM。

    • void clear(),清空废弃 View 堆,并将这些View从窗口中Detach。

    • void fillActiveViews(int childCount, int firstActivePosition),第一个参数为 mActiveViews要存储的最少的view的数量,第二个参数表示ListView中第一个可见元素的 position 值,调用这个方法后,会根据传入的参数将ListView中的指定元素存入mActiveView数组中

    • View getActiveView(int position),与 fillActiveViews() 相对应,用于从mActiveView数组中 获取对应 View,该方法接受一个 position 参数,表示元素在 ListView中的位置,方法内部会将 position 的值自动转化为mActiveView数组对应的下标值。特别之处在于,一旦找当指定位置相对应的 View,该 View 将被移除,下一次获取相同位置的 View 将会返回 null,即mActiveView不能被复用

    • void addScrapView(View scrap, int position) ,scrap 为要添加的 View,position 为 View 在父类中的位置。放入时位置赋给 scrappedFromPosition 。有 transient 状态的 view 不会被 scrap(废弃),会被加入mSkippedScrap。就是将移出可视区域的 view,设置它的scrappedFromPosition,然后从窗口中 detach 该 view,并根据 viewType 加入到mScrapView中。

      该方法会调用 mRecyclerListener 接口的函数 onMovedToScrapHeap

      mRecyclerListener 的设置可通过 AbsListView 的 setRecyclerListener方法。

      当 view 被回收准备再利用的时候设置要通知的监听器, 可以用来释放跟 view 有关的资源。

    • View getScrapView(int position),A view from the ScrapViews collection. These are unordered。内部实际上是调用 retrieveFromScrap() 方法。

    • View retrieveFromScrap(ArrayList scrapViews, int position)

      1.如果有 mAdapterHasStableIds ,并且适配器 position 的 itemId = (AbsListView.LayoutParams) view.getLayoutParams().itemId,则返回该 view

      2.如果有 view.scrappedFromPosition = position的,直接返回该view

      3.否则返回mScrapView中的最后一个

      4.如果缓存中没有 view,则返回 null

      第四种情况,ListView 稳定后,显示N个 item,此时mScrapView是没有缓存 view 的,当我们向上滑动一小块(第一个 item 并未移除屏幕),新的 view 将显示,此时ListView 会调用 Adapter.getView,但是缓存中没有,因此 convertView 是 null,因而需要分配一块内润来创建新的 convertView。若第一个 item 完全移除屏幕,第一个 view就会被detach,并加入到mScrapView,当我们继续滑动,需要显示新的 view,此时系统会从 mScrapView中找 position 对应的 view,由于尚未将第一个 view 加入到 mScrapView中,所以找不到,则从 mScrapView中取得最后一个缓存的 view 传递给 convertView。若第一个 item 完全移除屏幕且加入到 mScrapView中,向下滑动加载新的 item,则直接从缓存中查找 position 对应的 view 返回

    • void removeSkippedScrap(),清空mSkippedScrap

    • void scrapActiveViews() ,将mActiveViews 中剩余的 view 放入mScrapViews。实际上就是将mActiveView中未使用的 view 回收(因为,此时已经移出可视区域了)。会调用mRecyclerListener.onMovedToScrapHeap(scrap)

    • void fullyDetachScrapViews(),在布局阶段结束时,应重新附加所有临时分离视图或完全分离它们。 此方法可确保将mScrapViews中的所有剩余视图完全分离。

    • void pruneScrapViews(),确保mScrapViews的大小不超过mActiveViews的大小,如果适配器不回收其视图,则可能导致超出大小。如果超过,系统认为程序并没有复用convertView,而是每次都是创建一个新的view,为了避免产生大量的闲置内存且增加OOM的风险,系统会在每次回收后,去检查一下,将超过的部分释放掉,节约内存降低OOM风险。

    • void reclaimScrapViews(List views),将mScrapViews中的所有 View 放入提供的列表中。只看到有AbsListView.reclaimViews有调用到,但没有其它方法使用这个函数,可能在特殊情况下会使用到,但目前从framework中,看不出来。

    • void setCacheColorHint(int color),Updates the cache color hint of all known views.更新view的缓存颜色提示setDrawingCacheBackgroundColor。为所有的view绘置它们的背景色。

    附源代码:

    /**
         * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
         * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
         * start of a layout. By construction, they are displaying current information. At the end of
         * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
         * could potentially be used by the adapter to avoid allocating views unnecessarily.
         *
         * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
         * @see android.widget.AbsListView.RecyclerListener
         */
        class RecycleBin {
            @UnsupportedAppUsage
            private RecyclerListener mRecyclerListener;
    
            /**
             * The position of the first view stored in mActiveViews.
             */
            private int mFirstActivePosition;
    
            /**
             * Views that were on screen at the start of layout. This array is populated at the start of
             * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
             * Views in mActiveViews represent a contiguous range of Views, with position of the first
             * view store in mFirstActivePosition.
             */
            private View[] mActiveViews = new View[0];
    
            /**
             * Unsorted views that can be used by the adapter as a convert view.
             */
            private ArrayList<View>[] mScrapViews;
    
            private int mViewTypeCount;
    
            private ArrayList<View> mCurrentScrap;
    
            private ArrayList<View> mSkippedScrap;
    
            private SparseArray<View> mTransientStateViews;
            private LongSparseArray<View> mTransientStateViewsById;
    
            public void setViewTypeCount(int viewTypeCount) {
                if (viewTypeCount < 1) {
                    throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
                }
                //noinspection unchecked
                ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
                for (int i = 0; i < viewTypeCount; i++) {
                    scrapViews[i] = new ArrayList<View>();
                }
                mViewTypeCount = viewTypeCount;
                mCurrentScrap = scrapViews[0];
                mScrapViews = scrapViews;
            }
    
            public void markChildrenDirty() {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    final int scrapCount = scrap.size();
                    for (int i = 0; i < scrapCount; i++) {
                        scrap.get(i).forceLayout();
                    }
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        final int scrapCount = scrap.size();
                        for (int j = 0; j < scrapCount; j++) {
                            scrap.get(j).forceLayout();
                        }
                    }
                }
                if (mTransientStateViews != null) {
                    final int count = mTransientStateViews.size();
                    for (int i = 0; i < count; i++) {
                        mTransientStateViews.valueAt(i).forceLayout();
                    }
                }
                if (mTransientStateViewsById != null) {
                    final int count = mTransientStateViewsById.size();
                    for (int i = 0; i < count; i++) {
                        mTransientStateViewsById.valueAt(i).forceLayout();
                    }
                }
            }
    
            public boolean shouldRecycleViewType(int viewType) {
                return viewType >= 0;
            }
    
            /**
             * Clears the scrap heap.
             */
            @UnsupportedAppUsage
            void clear() {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    clearScrap(scrap);
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        clearScrap(scrap);
                    }
                }
    
                clearTransientStateViews();
            }
    
            /**
             * Fill ActiveViews with all of the children of the AbsListView.
             *
             * @param childCount The minimum number of views mActiveViews should hold
             * @param firstActivePosition The position of the first view that will be stored in
             *        mActiveViews
             */
            void fillActiveViews(int childCount, int firstActivePosition) {
                if (mActiveViews.length < childCount) {
                    mActiveViews = new View[childCount];
                }
                mFirstActivePosition = firstActivePosition;
    
                //noinspection MismatchedReadAndWriteOfArray
                final View[] activeViews = mActiveViews;
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                    // Don't put header or footer views into the scrap heap
                    if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                        // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
                        //        However, we will NOT place them into scrap views.
                        activeViews[i] = child;
                        // Remember the position so that setupChild() doesn't reset state.
                        lp.scrappedFromPosition = firstActivePosition + i;
                    }
                }
            }
    
            /**
             * Get the view corresponding to the specified position. The view will be removed from
             * mActiveViews if it is found.
             *
             * @param position The position to look up in mActiveViews
             * @return The view if it is found, null otherwise
             */
            View getActiveView(int position) {
                int index = position - mFirstActivePosition;
                final View[] activeViews = mActiveViews;
                if (index >=0 && index < activeViews.length) {
                    final View match = activeViews[index];
                    activeViews[index] = null;
                    return match;
                }
                return null;
            }
    
            View getTransientStateView(int position) {
                if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                    long id = mAdapter.getItemId(position);
                    View result = mTransientStateViewsById.get(id);
                    mTransientStateViewsById.remove(id);
                    return result;
                }
                if (mTransientStateViews != null) {
                    final int index = mTransientStateViews.indexOfKey(position);
                    if (index >= 0) {
                        View result = mTransientStateViews.valueAt(index);
                        mTransientStateViews.removeAt(index);
                        return result;
                    }
                }
                return null;
            }
    
            /**
             * Dumps and fully detaches any currently saved views with transient
             * state.
             */
            void clearTransientStateViews() {
                final SparseArray<View> viewsByPos = mTransientStateViews;
                if (viewsByPos != null) {
                    final int N = viewsByPos.size();
                    for (int i = 0; i < N; i++) {
                        removeDetachedView(viewsByPos.valueAt(i), false);
                    }
                    viewsByPos.clear();
                }
    
                final LongSparseArray<View> viewsById = mTransientStateViewsById;
                if (viewsById != null) {
                    final int N = viewsById.size();
                    for (int i = 0; i < N; i++) {
                        removeDetachedView(viewsById.valueAt(i), false);
                    }
                    viewsById.clear();
                }
            }
    
            /**
             * @return A view from the ScrapViews collection. These are unordered.
             */
            View getScrapView(int position) {
                final int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap < 0) {
                    return null;
                }
                if (mViewTypeCount == 1) {
                    return retrieveFromScrap(mCurrentScrap, position);
                } else if (whichScrap < mScrapViews.length) {
                    return retrieveFromScrap(mScrapViews[whichScrap], position);
                }
                return null;
            }
    
            /**
             * Puts a view into the list of scrap views.
             * <p>
             * If the list data hasn't changed or the adapter has stable IDs, views
             * with transient state will be preserved for later retrieval.
             *
             * @param scrap The view to add
             * @param position The view's position within its parent
             */
            void addScrapView(View scrap, int position) {
                final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
                if (lp == null) {
                    // Can't recycle, but we don't know anything about the view.
                    // Ignore it completely.
                    return;
                }
    
                lp.scrappedFromPosition = position;
    
                // Remove but don't scrap header or footer views, or views that
                // should otherwise not be recycled.
                final int viewType = lp.viewType;
                if (!shouldRecycleViewType(viewType)) {
                    // Can't recycle. If it's not a header or footer, which have
                    // special handling and should be ignored, then skip the scrap
                    // heap and we'll fully detach the view later.
                    if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                        getSkippedScrap().add(scrap);
                    }
                    return;
                }
    
                scrap.dispatchStartTemporaryDetach();
    
                // The the accessibility state of the view may change while temporary
                // detached and we do not allow detached views to fire accessibility
                // events. So we are announcing that the subtree changed giving a chance
                // to clients holding on to a view in this subtree to refresh it.
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
    
                // Don't scrap views that have transient state.
                final boolean scrapHasTransientState = scrap.hasTransientState();
                if (scrapHasTransientState) {
                    if (mAdapter != null && mAdapterHasStableIds) {
                        // If the adapter has stable IDs, we can reuse the view for
                        // the same data.
                        if (mTransientStateViewsById == null) {
                            mTransientStateViewsById = new LongSparseArray<>();
                        }
                        mTransientStateViewsById.put(lp.itemId, scrap);
                    } else if (!mDataChanged) {
                        // If the data hasn't changed, we can reuse the views at
                        // their old positions.
                        if (mTransientStateViews == null) {
                            mTransientStateViews = new SparseArray<>();
                        }
                        mTransientStateViews.put(position, scrap);
                    } else {
                        // Otherwise, we'll have to remove the view and start over.
                        clearScrapForRebind(scrap);
                        getSkippedScrap().add(scrap);
                    }
                } else {
                    clearScrapForRebind(scrap);
                    if (mViewTypeCount == 1) {
                        mCurrentScrap.add(scrap);
                    } else {
                        mScrapViews[viewType].add(scrap);
                    }
    
                    if (mRecyclerListener != null) {
                        mRecyclerListener.onMovedToScrapHeap(scrap);
                    }
                }
            }
    
            private ArrayList<View> getSkippedScrap() {
                if (mSkippedScrap == null) {
                    mSkippedScrap = new ArrayList<>();
                }
                return mSkippedScrap;
            }
    
            /**
             * Finish the removal of any views that skipped the scrap heap.
             */
            void removeSkippedScrap() {
                if (mSkippedScrap == null) {
                    return;
                }
                final int count = mSkippedScrap.size();
                for (int i = 0; i < count; i++) {
                    removeDetachedView(mSkippedScrap.get(i), false);
                }
                mSkippedScrap.clear();
            }
    
            /**
             * Move all views remaining in mActiveViews to mScrapViews.
             */
            void scrapActiveViews() {
                final View[] activeViews = mActiveViews;
                final boolean hasListener = mRecyclerListener != null;
                final boolean multipleScraps = mViewTypeCount > 1;
    
                ArrayList<View> scrapViews = mCurrentScrap;
                final int count = activeViews.length;
                for (int i = count - 1; i >= 0; i--) {
                    final View victim = activeViews[i];
                    if (victim != null) {
                        final AbsListView.LayoutParams lp
                                = (AbsListView.LayoutParams) victim.getLayoutParams();
                        final int whichScrap = lp.viewType;
    
                        activeViews[i] = null;
    
                        if (victim.hasTransientState()) {
                            // Store views with transient state for later use.
                            victim.dispatchStartTemporaryDetach();
    
                            if (mAdapter != null && mAdapterHasStableIds) {
                                if (mTransientStateViewsById == null) {
                                    mTransientStateViewsById = new LongSparseArray<View>();
                                }
                                long id = mAdapter.getItemId(mFirstActivePosition + i);
                                mTransientStateViewsById.put(id, victim);
                            } else if (!mDataChanged) {
                                if (mTransientStateViews == null) {
                                    mTransientStateViews = new SparseArray<View>();
                                }
                                mTransientStateViews.put(mFirstActivePosition + i, victim);
                            } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                                // The data has changed, we can't keep this view.
                                removeDetachedView(victim, false);
                            }
                        } else if (!shouldRecycleViewType(whichScrap)) {
                            // Discard non-recyclable views except headers/footers.
                            if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                                removeDetachedView(victim, false);
                            }
                        } else {
                            // Store everything else on the appropriate scrap heap.
                            if (multipleScraps) {
                                scrapViews = mScrapViews[whichScrap];
                            }
    
                            lp.scrappedFromPosition = mFirstActivePosition + i;
                            removeDetachedView(victim, false);
                            scrapViews.add(victim);
    
                            if (hasListener) {
                                mRecyclerListener.onMovedToScrapHeap(victim);
                            }
                        }
                    }
                }
                pruneScrapViews();
            }
    
            /**
             * At the end of a layout pass, all temp detached views should either be re-attached or
             * completely detached. This method ensures that any remaining view in the scrap list is
             * fully detached.
             */
            void fullyDetachScrapViews() {
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    for (int j = scrapPile.size() - 1; j >= 0; j--) {
                        final View view = scrapPile.get(j);
                        if (view.isTemporarilyDetached()) {
                            removeDetachedView(view, false);
                        }
                    }
                }
            }
    
            /**
             * Makes sure that the size of mScrapViews does not exceed the size of
             * mActiveViews, which can happen if an adapter does not recycle its
             * views. Removes cached transient state views that no longer have
             * transient state.
             */
            private void pruneScrapViews() {
                final int maxViews = mActiveViews.length;
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    int size = scrapPile.size();
                    while (size > maxViews) {
                        scrapPile.remove(--size);
                    }
                }
    
                final SparseArray<View> transViewsByPos = mTransientStateViews;
                if (transViewsByPos != null) {
                    for (int i = 0; i < transViewsByPos.size(); i++) {
                        final View v = transViewsByPos.valueAt(i);
                        if (!v.hasTransientState()) {
                            removeDetachedView(v, false);
                            transViewsByPos.removeAt(i);
                            i--;
                        }
                    }
                }
    
                final LongSparseArray<View> transViewsById = mTransientStateViewsById;
                if (transViewsById != null) {
                    for (int i = 0; i < transViewsById.size(); i++) {
                        final View v = transViewsById.valueAt(i);
                        if (!v.hasTransientState()) {
                            removeDetachedView(v, false);
                            transViewsById.removeAt(i);
                            i--;
                        }
                    }
                }
            }
    
            /**
             * Puts all views in the scrap heap into the supplied list.
             */
            void reclaimScrapViews(List<View> views) {
                if (mViewTypeCount == 1) {
                    views.addAll(mCurrentScrap);
                } else {
                    final int viewTypeCount = mViewTypeCount;
                    final ArrayList<View>[] scrapViews = mScrapViews;
                    for (int i = 0; i < viewTypeCount; ++i) {
                        final ArrayList<View> scrapPile = scrapViews[i];
                        views.addAll(scrapPile);
                    }
                }
            }
    
            /**
             * Updates the cache color hint of all known views.
             *
             * @param color The new cache color hint.
             */
            void setCacheColorHint(int color) {
                if (mViewTypeCount == 1) {
                    final ArrayList<View> scrap = mCurrentScrap;
                    final int scrapCount = scrap.size();
                    for (int i = 0; i < scrapCount; i++) {
                        scrap.get(i).setDrawingCacheBackgroundColor(color);
                    }
                } else {
                    final int typeCount = mViewTypeCount;
                    for (int i = 0; i < typeCount; i++) {
                        final ArrayList<View> scrap = mScrapViews[i];
                        final int scrapCount = scrap.size();
                        for (int j = 0; j < scrapCount; j++) {
                            scrap.get(j).setDrawingCacheBackgroundColor(color);
                        }
                    }
                }
                // Just in case this is called during a layout pass
                final View[] activeViews = mActiveViews;
                final int count = activeViews.length;
                for (int i = 0; i < count; ++i) {
                    final View victim = activeViews[i];
                    if (victim != null) {
                        victim.setDrawingCacheBackgroundColor(color);
                    }
                }
            }
    
            private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
                final int size = scrapViews.size();
                if (size > 0) {
                    // See if we still have a view for this position or ID.
                    // Traverse backwards to find the most recently used scrap view
                    for (int i = size - 1; i >= 0; i--) {
                        final View view = scrapViews.get(i);
                        final AbsListView.LayoutParams params =
                                (AbsListView.LayoutParams) view.getLayoutParams();
    
                        if (mAdapterHasStableIds) {
                            final long id = mAdapter.getItemId(position);
                            if (id == params.itemId) {
                                return scrapViews.remove(i);
                            }
                        } else if (params.scrappedFromPosition == position) {
                            final View scrap = scrapViews.remove(i);
                            clearScrapForRebind(scrap);
                            return scrap;
                        }
                    }
                    final View scrap = scrapViews.remove(size - 1);
                    clearScrapForRebind(scrap);
                    return scrap;
                } else {
                    return null;
                }
            }
    
            private void clearScrap(final ArrayList<View> scrap) {
                final int scrapCount = scrap.size();
                for (int j = 0; j < scrapCount; j++) {
                    removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
                }
            }
    
            private void clearScrapForRebind(View view) {
                view.clearAccessibilityFocus();
                view.setAccessibilityDelegate(null);
            }
    
            private void removeDetachedView(View child, boolean animate) {
                child.setAccessibilityDelegate(null);
                AbsListView.this.removeDetachedView(child, animate);
            }
        }
    
  • 相关阅读:
    线段树专辑—— pku 1436 Horizontally Visible Segments
    线段树专辑——pku 3667 Hotel
    线段树专辑——hdu 1540 Tunnel Warfare
    线段树专辑—— hdu 1828 Picture
    线段树专辑—— hdu 1542 Atlantis
    线段树专辑 —— pku 2482 Stars in Your Window
    线段树专辑 —— pku 3225 Help with Intervals
    线段树专辑—— hdu 1255 覆盖的面积
    线段树专辑—— hdu 3016 Man Down
    Ajax跨域访问
  • 原文地址:https://www.cnblogs.com/huaranmeng/p/13781628.html
Copyright © 2011-2022 走看看