zoukankan      html  css  js  c++  java
  • Android Architecture Components 系列(三) LiveData

        一、LiveData 定义
        LiveData 是一种持有可被观察数据的类。LivaData是有生命周期感知能力的,可以在Activity、Fragment、Services生命周期是活跃状态时更新组件。
       LiveData 实际上就是一个 Data Holder类,既可以持有数据,也可以被监听,当数据改变时候,可以触发回调。与Observable不同的是,LiveData绑定了App组件的生命周期,可以在指定在LifeCycle的某个状态被触发。
        上一文提到 START 和RESUMED 状态是活跃状态,才能激活LiveData的使用使其通知数据变化。
    所以使用LiveData就需要配合实现前文提到的LifecycleOwner对象。当 对应的生命周期对象DESTROY时候才能移除观察者Observer 。
     
    ps:这里对生命周期函数的管理尤为重要,因为生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄露等问题。
     
       二、 LiveData 优点 
        1、UI和实时数据保持一致,因为LiveData采用饿是观察者模式,这样就可以在数据发生改变时候获得通知更新UI
        2、避免内存泄露 ,观察者被绑定到组件的生命周期上,当被绑定的组件被销毁时,观察者会立刻自动清理掉自身的数据
        3、不会再产生由于Activity 处于Stop状态 而引起的崩溃,当Activity处于后台状态时,是不会收到LiveData的任何event事件的
        4、不需要再手动解决生命周期带来的问题, Livedata可以感知被绑定的组件的生命周期,只有在两种活跃状态才会通知数据变化
        5、实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
        6、解决Configuration Change 屏幕旋转问题,在屏幕发生旋转或是被回收再启动时,立刻能够获取到最新的数据
        7、数据共享,如果对应的LiveData时单例的话,就能在App的组件间分享数据;这部分详细的信息可以参考继承LiveData
            
       三、 LiveData 使用
    • 创建一个持有某种数据类型的LiveData (通常在ViewModel中创建)
        创建一个LiveData对象    
         LiveData是一个数据包装类,具体的包装对象可以是任何数据,包括集合(List,arrayList)。LiveData通常在ViewModel中创建,然后通过gatter方法获取。代码如下:
     
    public class NameViewModelextends ViewModel{ 
        // Create a LiveData with a String 
        //暂时就把MutableLiveData看成是LiveData吧,下面的文章有详细的解释 
        private MutableLiveData<String> mCurrentName; 
        public MutableLiveData<String> getCurrentName() {
             if (mCurrentName == null) { 
                mCurrentName = new MutableLiveData<String>(); 
                } 
                return mCurrentName; 
            } 
        // Rest of the ViewModel… 
       }
    • 创建一个定义了onChange()方法的观察者;这个方法是控制LiveData中数据发生变化时候,采取什么措施(更新界面或是其他)(通常在Activity or Fragment 这样的UI Controller中创建这个观察者)
    观察LiveData中的数据
    通常情况下都是在组件的onCreate()方法中开始观察数据,因为:
        a、系统会多次调用onResume()方法
        b、确保Activity 或是 Fragment在处于活跃状态时候立刻可以展示需要的数据
     
    public class NameActivity extends AppCompatActivityimplements LifecycleOwner  { 
        private NameViewModelmModel; 
        @Override 
        protected void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            // Other code to setup or init the activity
                        ...
                    // Get the ViewModel. 对象 
                mModel = ViewModelProviders.of(this).get(NameViewModel.class); 
                    // Create the observer which updates the UI. 创建一个观察者用来更新UI操作
                final Observer<String>nameObserver = new Observer<String>() { 
                    @Override 
                    public void onChanged(@Nullable final String newName) {
                      // Update the UI, in this case, a TextView. 
                            mNameTextView.setText(newName); 
                        } 
                };
             // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.   
            //通过observe()方法将liveData和 观察者ViewModel 进行连接
                           mModel.getCurrentName().observe(this, nameObserver); 
        } 
             @Override 
        publicLifecycleRegistrygetLifecycle() { 
            returnlifecycleRegistry; 
        }
    }
     
    • 通过observe()方法连接观察者和LiveData ;observe()需要携带一个LifecycleOwner类,这样就可以让那个观察者订阅LiveData中的数据,在onChange()中实现实时更新。
    更新LiveData对象
        若想要在 Activity 或是 Fragment 中改变 LiveData的数据值呢?(通过用户交互事件改变LiveData中的值)。
        这时候需要通过Architecture Component提供的MutableLiveData类来完成需求。MutableLiveData是LiveData一个子类。
    mButton.setOnClickListener(new OnClickListener() { 
        @Override public void onClick(View v) { 
            String anotherName = "John Doe”; 
            mModel.getCurrentName().setValue(anotherName); 
            } 
       });
    解析:
    调用setValue()方法就可以把LiveData中的值改为John Doe。
    同样,通过这种方法修改LiveData中的值同样会触发所有对这个数据感兴趣的类。
    思考:那么setValue()和postValue()有什么不同呢?
    答:区别就是setValue()只能在主线程中调用,而postValue()可以在子线程中调用。
     
    再来看如下实例代码:
    publicclass MainActivity extends AppCompatActivity implements LifecycleRegistryOwner
        privateLifecycleRegistry lifecycleRegistry =newLifecycleRegistry(this); 
        @Override
            protectedvoidonCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.activity_main); 
            // MutableLiveData 
            UserData userData =newUserData(); 
            userData.observe(this,newObserver() { 
                @Override 
                publicvoidonChanged(@Nullable Object o) { 
                Log.e("UserData","onChanged"); 
                //do Sth to update
                } 
            }); 
            userData.setValue(""); 
        }
        @Override 
        publicLifecycleRegistrygetLifecycle() { 
            returnlifecycleRegistry; 
        }
    }
    //UserData 就是ViewModel的 model数据
    publicclass UserData extends LiveData implements LifecycleObserver {
        privatestaticfinalString TAG ="UserData”; 
        publicUserData() { 
        } 
        @Override 
        protectedvoidonActive() {
            super.onActive(); 
            Log.e(TAG,"onActive"); 
        } 
        @Override 
        protectedvoidonInactive() { 
            super.onInactive(); 
            Log.e(TAG,"onInactive"); 
        } 
        @Override 
        protectedvoidsetValue(Object value) { 
            super.setValue(value); 
            Log.e(TAG,"setValue"); 
        } 
     }
    执行效果:
    02-2614:43:54.44420885-20885/xxxxxx E/UserData: setValue
    02-2614:43:54.45620885-20885/xxxxxx E/UserData: onActive 
    02-2614:43:54.45620885-20885/xxxxxx E/UserData: onChanged 
    02-2614:51:11.97420885-20885/xxxxxx E/UserData: onInactive 
    02-2614:51:23.30420885-20885/xxxxxx E/UserData: onActive 
    02-2614:51:23.31720885-20885/xxxxxx E/UserData: onInactive
    解析:
        onActive(): 这个方法在LiveData 在被激活的状态下执行 ,可以开始执行一些操作
        onInActive(): 这个方法在LiveData在失去活性状态下执行,可以结束执行一些操作
        setValue():执行该方法的时候,LiveData 可以触发它的回调函数
     
    上面 可以看到在Ui Controller层 LiveData是通过 observe,那observe 又做了什么呢?
     
    (一)添加观察者
    分析observe() 源码可以看到:
     
    /** 
    * Adds the given observer to the observers list within the lifespan of the given 
    * owner. The events are dispatched on the main thread. If LiveData already has data 
    * set, it will be delivered to the observer. 
    * <p> 
    The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED} 
    * or {@link Lifecycle.State#RESUMED} state (active). 
    * <p> 
    * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will 
    * automatically be removed. 
    * <p> 
    * When data changes while the {@code owner} is not active, it will not receive any updates. 
    * If it becomes active again, it will receive the last available data automatically. 
    * <p> 
    * LiveData keeps a strong reference to the observer and the owner as long as the 
    * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to 
    * the observer &amp; the owner. 
    * <p> 
    * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData ignores the call. 
    * <p> 
    * If the given owner, observer tuple is already in the list, the call is ignored. 
    * If the observer is already in the list with another owner, LiveData throws an 
    * {@link IllegalArgumentException}. 
    * * @param owner The LifecycleOwLifecycle.State#STARTEDner which controls the observer 
    * @param observer The observer that will receive the events 
    */ 
    @MainThread 
    publicvoidobserve(LifecycleOwner owner, Observer<T> observer) { 
        if(owner.getLifecycle().getCurrentState() == DESTROYED) { //1
            // ignore 
            return; 
        } 
        LifecycleBoundObserver wrapper =newLifecycleBoundObserver(owner, observer); //2
        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper); 
            if(existing !=null&& existing.owner != wrapper.owner) {  //3
                thrownewIllegalArgumentException("Cannot add the same observer" 
                        +" with different lifecycles"); 
            } 
            if(existing !=null) { 
                return; 
            } 
            owner.getLifecycle().addObserver(wrapper);  //4
            wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState())); //5
       }
     
     
    源码解析:
        (1)active()方法的回调是基于 Owner的 [@link Lifecycle.State#STARTED] 或是 [{@link Lifecycle.State#RESUMED} state (active)])
        (2) 如果 Owner 状态转换至 destroy状态 ,将会自动移除 observer对象
        (3) 如果 Owner 状态已经处于destroy状态 ,那么LiveData 则会自动被过滤掉
     
    1. 先判断一下LifecycleRegistry当前的状态,如果是DESTORYED的话,就直接返回。
    2. 之后是将LifecycleOwner和创建Observer封装到LifecycleBoundObserver中。
    3. 从当前的Oberver集合中查找没有传入的Observer对应的包装类,如果有则返回,没有则添加。
    4. LifecycleRegistry添加包装之后的LifecycleBoundObserver观察者。
    5. 更新下当前的包装类的状态。
            ps:这里需要理解并记住的是LifecycleBoundObserver是一个拥有真正回调Observer和LifecycleOwner的封装类。
    然后这里的addObserver() 方法又进行了什么操作呢?
        @Override
        public void addObserver(LifecycleObserver observer){
            State initialState = mStatet == DESTROY ? DESTROY :INITIALIZED;
            ObserverWithState statefulObserver = ObserverWith (observer, initialState);
            ObserverWithState previous = mObserverMap.putIfAbsent(observer , statefulObserver );
            
            if( previous !=null) {
                return;
            }
            boolean  isReentrance = mAddingObserverCounter !=0 || mHandlingEvent;
            State targetState = calculateTargetState (observer);
            mAddingObserverCounter ++ ;
            while ( ( statefulObserver.mState.compareTo (targetState) < 0
                        && mObserverMap.contains(observer) ) ) {
                pushParentState( statefulObserver.mState);
                statefulObserver.dispatchEvent(mLifecycleOwner,upEvent(statefulObserver.mState));
                popParentState();
                targetState = calculateTargetState (observer );
             }
                if(!isReentrance ){
                    sync()
                }
            mAddingObserverCounter - - ;
    }
        在LifecycleRegistry中添加观察者,这个LifecycleRegistry是在Activity/Fragment中创建的成员变量。
    • 确定初始时LifecycleBoundObserverd的状态,这里大部分的情况都是INITIALIZED,除非把之前的observe写在onDestory中,不过估计一般没人这么写。
    • 将传入的LifecycleBoundObserver和确定的状态封装到一个statefulObserver。在这个过程中会对observer进行一定转化,将其改变成另一种LifecycleObserver,然后再使用的时候会通过反射去调到实际需要的方法。
    • 将封装过的statefulObserver和传入的observer添加到当前的一个map中进行保存,如果之前已经添加过的话,就直接返回旧的,没有的话再放入,返回null。
    • 判断是否可以重入,来决定是否进行同步,这里的parentState暂时先不考虑,等最后的时候再分析。
    • 其中while循环的部分是为了修改刚添加进去的ObseverWithState中state的状态。
    • sync方法是事件传递的关键,在之后也会用到,就先不分析。
     
    (二)生命周期改变
            当应用的生命周期变化的时候,会发送对应的生命周期事件到LifecycleRegistry的 handleLifecycleEvent 方法中进行处理 。
    /**
    * Sets the current state and notifies the observers.
    * <p>
    * Note that if the {@code currentState} is the same state as the last call to this method,
    * calling this method has no effect.
    * @param event The event that was received
    */
    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
        State next = getStateAfter(event);
        moveToState(next);
    }
    private void moveToState(State next) {
        if (mState == next) {
            return;
        }
        mState= next;
        if (mHandlingEvent || mAddingObserverCounter != 0) {
            mNewEventOccurred = true;
           // we will figure out what to do on upper level.
            return;
        }
        mHandlingEvent = true;
        sync();
        mHandlingEvent = false;
    }
    首先设置当前的LifecycleRegistry 中的 mState值,之后执行synv方法
      
    // happens only on the top of stack (never in reentrance),
    // so it doesn't have to take in account parents
    private void sync() {
        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
        if (lifecycleOwner == null) {
            Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
                    + "new events from it.");
            return;
        }
        while (!isSynced()) { //进行同步
            mNewEventOccurred = false;
            // no need to check eldest for nullability, because isSynced does it for us.
           
            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
                backwardPass(lifecycleOwner); //逆推计算
            }
            
        Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
            if (!mNewEventOccurred && newest != null
                    && mState.compareTo(newest.getValue().mState) > 0) {
                forwardPass(lifecycleOwner); //正推计算
            }
        }
        mNewEventOccurred = false;
    }
    解析: 该方法先判断是否可以进行同步操作,判断条件是当前map中的oberver数量和状态,之后会根据当前的mObserverMap中保存的Observer的状态和当前的Registry的状态进行对比,来决定是进行正推计算还是反推计算。
         正推计算:
    private void forwardPass(LifecycleOwner lifecycleOwner) {
        Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
                mObserverMap.iteratorWithAdditions();
        while (ascendingIterator.hasNext() && !mNewEventOccurred) {
            Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next();
            ObserverWithState observer= entry.getValue();
            while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
                    && mObserverMap.contains(entry.getKey()))) {
                pushParentState(observer.mState);
                observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
                popParentState();
            }
        }
    }
    解析:把当前的observerMap中的数据进行迭代 ,判断状态之后执行 observer.dispatchEvent() 方法进行同步Observer 操作
    void dispatchEvent(LifecycleOwner owner, Event event) {
        State newState = getStateAfter(event);
        mState = min(mState, newState);
        mLifecycleObserver.onStateChanged(owner, event);
        mState = newState;
    }
    在这里先设置当前Observer的状态通过getStateAfter(),之后调用Observer的onStateChanged方法,这个方法最终会通过反射原理,最终调到LiveData中的 LifecycleBoundObserver的 onStateChange()方法
     
    (三)数据更新
     
    onChange()调用链 基本如:
     setValue(T value ) —> dispatchingValue(@Nullable LifecycleBoundObserver initiator ) —> considerNotify(LifecycleBoundObserver observer  —> onChange)
     
    首先看一眼这个setValue()方法
    /**
    * Sets the value. If there are active observers, the value will be dispatched to them.
    * <p>
    * This method must be called from the main thread. If you need set a value from a background
    * thread, you can use {@link #postValue(Object)}
    *
    * @param value The new value
    */
    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }
    将成员变量mData赋值,之后调用了dispatchingValue()方法
    在这个dispatchingValue方法里面又干了什么呢?
     
    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator); //importing
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
    这里关键方法considerNotify ,如果是通过setValue方法进行更新的话,会更新所有的observer;
    注意,如果是通过前面的handleLifecycleEvent方法进行更新的话,那么只会更改当前的observer。
     
     
     
    下面再看下 onChange()方法 的源码:
        privatevoidconsiderNotify(LifecycleBoundObserver observer) { 
            if(!observer.active) {
                return; 
            } 
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. 
        //  we still first check observer.active to keep it as the entrance for events. So even if 
        // the observer moved to an active state, if we've not received that event, we better not 
        // notify for a more predictable notification order. 
            if(!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
               observer.activeStateChanged(false); 
                    return; 
            } 
            if(observer.lastVersion >= mVersion) { 
                    return; 
            } 
                observer.lastVersion = mVersion; 
            //noinspection unchecked 
                observer.observer.onChanged((T) mData); 
        }
    •  首先检查当前的observer的active 之后会检查observer的owner的状态是否可用
    •  其次判断当前版本,最后更新数据
        
    上面已经知道LiveData的活跃状态是 STARTED 和 RESUMED 。那么如何在“活跃状态下传递数据呢”? 
    下面是实例代码:
    public class StockLiveData extends LiveData<BigDecimal> { 
        private StockManager mStockManager; 
        private SimplePriceListener mListener = new SimplePriceListener() { 
            @Override 
            public void onPriceChanged(BigDecimal price) { 
                setValue(price); 
            } 
        }; 
        public StockLiveData(String symbol) { 
            mStockManager = new StockManager(symbol);
         } 
            @Override 
            protected void onActive() {  //活跃状态回调
                mStockManager.requestPriceUpdates(mListener); 
        } 
            @Override 
            protected void onInactive() { //非活跃状态回调
                mStockManager.removeUpdates(mListener); 
            } 
    }
    如果把StockLiveData写成单例模式,那么还可以在不同的组件间共享数据。代码如下:
    public class StockLiveData extends LiveData<BigDecimal> { 
        private static StockLiveData sInstance; 
        private StockManager mStockManager; 
        private SimplePriceListener mListener = new SimplePriceListener() { 
            @Override 
            public void onPriceChanged(BigDecimal price) { 
                setValue(price);
                 } 
         }; 
        @MainThread 
        public static StockLiveData get(String symbol) { 
            if (sInstance == null) { 
                sInstance = new StockLiveData(symbol); 
            } 
                return sInstance; 
            }
             private StockLiveData(String symbol) { 
                mStockManager = new StockManager(symbol); 
            } 
            @Override 
            protected void onActive() { 
                mStockManager.requestPriceUpdates(mListener); 
            } 
            @Override 
            protected void onInactive() { 
                mStockManager.removeUpdates(mListener);
             } 
        }
        四、LiveData 的 Transformations 
         一般情况下再使用LiveData的时候都是它的子类MutableLiveData。有时候需要对一个LiveData做observer,但是这个LiveData又和另一个LiveData有依赖关系,类似于RxJava中的操作符。这时候需要这样进行:在LiveData的数据被分发到各个组件之前转换的值,但是LiveData里面的数据本身的值并没有改变。
    •     Transformations.map() 
     用于事件流的传递,用于触发下游数据
        LiveData <User> userLiveData = … ;
        LiveData<String> userName = Transformations.map (userLiveData ,user -> {
        user.name + “” + user.lastName });
        把原来是包含User的 LiseData转换 成包含 String 的LiveData传递出去    
     
    •    Transformations.switchMap()
     用于事件流的传递,用于触发上游数据
        private LiveData<User> getUser(String id){…}
        LiveData<String> userId = … ;
        LiveData<User> user = Transformations.switchMap (userId ,id -> getUser(id) );
        switchMap()区别于map() 的是,传递给switchMap()的函数必须返回LiveData对象。类似于LiveData ,Transformations也可以在观察者的整个生命中存在。只有在观察者处于观察LiveData状态时,Transformations才会运算,其是延迟运算 lazily calculated ,而生命周期感知能力确保不会因为延迟发生任何问题。
     Transformations的使用场景
            假设在ViewModel对象内部需要一个 Lifecycle对象,此时推荐使用Transformations。比如
     有个Ui组件接收用户输入的地址,返回对应的邮编。 那么 代码如下:
        class MyViewModel extends ViewModel {
            private final PostalCodeRepository repository ;
            public MyViewModel( PostalCodeRepository repository ){
                    this.repository= repository;
            }
            private LiveData <String> getPostalCode (String address ){
                    //Don’t do this 
                    return repository.getPostCode( address );
            }
    }
            看代码中的注释,有个// DON'T DO THIS (不要这么干),这是为什么?有一种情况是如果UI组件被回收后又被重新创建,那么又会触发一次 repository.getPostCode(address)查询,而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:
          class MyViewModel extends ViewModel { 
            private final PostalCodeRepository repository; 
            private final MutableLiveData<String> addressInput = new MutableLiveData(); 
            
            public final LiveData<String> postalCode = 
                        Transformations.switchMap(addressInput, (address) -> {             
                            return repository.getPostCode(address);  //在输入变化时才会调用
            });
             public MyViewModel(PostalCodeRepository repository) { 
                    this.repository = repository 
            } 
            private void setInput(String address) { 
                    addressInput.setValue(address);
             } 
        }
        解析:
        postalCode这个变量存在的作用是把输入的addressInput转换成邮编,那么只有在输入变化时才会调用repository.getPostCode()方法。这就好比你用final来修饰一个数组,虽然这个变量不能再指向其他数组,但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下,用了 switchMap() 可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。
     
    延伸扩展 合并多个LiveData中的数据
        
        MediatorLiveDate 
            其是LiveData 的子类 ,可以通过MediatorLiveDate合并都个LiveData数据源。同样任意一个来源的LiveData数据发生变化,MediatorLiveDate 都会通知观察它的对象。
     
     
        五、小结
    •  LiveData的优点
    •  LiveData的使用
    •  LiveData 和 Owner的联结器 observe方法
    •  LiveData的三个重要方法
      • onActive的作用 
        • 当生命周期处于onStart,onResume是被激活 ,且当这个方法被调用时,表示LiveData的观察者开始使用了,也就是说应该注册事件监听了
      • onInActive的作用
        • 当生命周期处于onDestroy时激活,且当这个方法被调用时,表示LiveData的观察者停止使用了,此时应该将监听器移除
      • setValue的作用
        • 当调用此方法时,会激活Observer内的onChange方法,且通过调用这个方法来更新LiveData的数据,并通知处于活动状态的观察者
    • LiveData的转换
    系列文章列表:
  • 相关阅读:
    requets中urlencode的问题
    洛谷$P4503 [CTSC2014]$企鹅$QQ$ 哈希
    洛谷$P5446 [THUPC2018]$绿绿和串串 $manacher$
    洛谷$P5329 [SNOI2019]$字符串 字符串
    洛谷$P1390$ 公约数的和 欧拉函数
    洛谷$P4318$ 完全平方数 容斥+二分
    入门懵逼钨丝繁衍
    $ CometOJ-Contest#11 D$ $Kruscal$重构树
    洛谷$P4884$ 多少个1? 数论
    入门数论简单总结
  • 原文地址:https://www.cnblogs.com/cold-ice/p/9115824.html
Copyright © 2011-2022 走看看