[NOI2019]I君的探险
I君的探险
题解
首先看到下面这一大串奇形怪状的数据范围,可以猜测这道题是需要分类讨论的。
首先看 
     
      
       
       
         n 
        
       
         ⩽ 
        
       
         500 
        
       
      
        n\leqslant 500 
       
      
    n⩽500的部分分,显然有 
     
      
       
        
        
          L 
         
        
          q 
         
        
       
         ⩾ 
        
        
         
         
           n 
          
         
           ( 
          
         
           n 
          
         
           − 
          
         
           1 
          
         
           ) 
          
         
        
          2 
         
        
       
      
        L_q\geqslant \frac{n(n-1)}{2} 
       
      
    Lq⩾2n(n−1),可以尝试用一种比较暴力的方法,就是修改完每个点后查询一下编号比它大的每个点是否改变颜色,显然改变了这两个点之间就右边,否则没边。
 显然,这样询问次数是 
     
      
       
        
        
          1 
         
        
          2 
         
        
       
         n 
        
       
         ( 
        
       
         n 
        
       
         − 
        
       
         1 
        
       
         ) 
        
       
      
        \frac{1}{2}n(n-1) 
       
      
    21n(n−1)的,可以拿到 
     
      
       
       
         20 
        
       
         p 
        
       
         t 
        
       
         s 
        
       
      
        20pts 
       
      
    20pts。
对于性质 
     
      
       
       
         A 
        
       
      
        A 
       
      
    A,不难发现这相当于将所有的点两两配对。
 如何求出这些点是怎么配对的,一种经典的方法是二进制分类。
 每次将所有点根据这个二进制位上是否为 
     
      
       
       
         1 
        
       
      
        1 
       
      
    1分成两个集合,修改其中一个集合就可以就可以知道与每个点配对的点在这一位上是否为 
     
      
       
       
         1 
        
       
      
        1 
       
      
    1。
 这样的话我们的修改次数与询问次数就都是 
     
      
       
       
         O 
        
        
        
          ( 
         
        
          n 
         
        
          log 
         
        
           
         
        
          n 
         
        
          ) 
         
        
       
      
        O\left(n\log n\right) 
       
      
    O(nlogn)的了。
再看性质 
     
      
       
       
         B 
        
       
      
        B 
       
      
    B,一种比较容易想到的方法是对于每个点去二分它的父亲节点的范围,操作玩它 
     
      
       
       
         [ 
        
       
         l 
        
       
         , 
        
       
         m 
        
       
         i 
        
       
         d 
        
       
         ] 
        
       
      
        [l,mid] 
       
      
    [l,mid]区间内的所有点观察这个点颜色是否改变来往下二分。
 显然,我们不太可能对于每个点的询问时都去完整操作一次 
     
      
       
       
         [ 
        
       
         l 
        
       
         , 
        
       
         m 
        
       
         i 
        
       
         d 
        
       
         ] 
        
       
      
        [l,mid] 
       
      
    [l,mid],所以得考虑整体二分,将询问反正 
     
      
       
       
         m 
        
       
         i 
        
       
         d 
        
       
      
        mid 
       
      
    mid上,从 
     
      
       
       
         1 
        
       
      
        1 
       
      
    1扫到 
     
      
       
       
         m 
        
       
         i 
        
       
         d 
        
       
      
        mid 
       
      
    mid时看它是否改变。
 但我们可以发现一个问题,就是不止整个点的父亲,它的儿子也会贡献到它。
 也就是说如果我们每次都从 
     
      
       
       
         0 
        
       
      
        0 
       
      
    0扫到 
     
      
       
       
         n 
        
       
         − 
        
       
         1 
        
       
      
        n-1 
       
      
    n−1的时候都必须先询问一下每个点的颜色。
 这样不就 
     
      
       
       
         2 
        
       
         n 
        
       
         log 
        
       
          
        
       
         n 
        
       
      
        2n\log n 
       
      
    2nlogn了吗?考虑优化。
 我们都知道做莫队时有一个经典的优化,奇数的块从左扫到右,偶数个块的时候再从右扫回来,这个优化同样也可以用在这里。
 奇数次二分的时候扫过去,偶数次的时候扫回来就可以将我们的询问次数成功变到 
     
      
       
       
         n 
        
       
         log 
        
       
          
        
       
         n 
        
       
      
        n\log n 
       
      
    nlogn了,因为这样实际被操作了的点只有询问的哪个前缀,其它都被抵消掉了,就没必要每次开始前都询问每个点颜色一次。
 当然,操作的次数也是 
     
      
       
       
         n 
        
       
         log 
        
       
          
        
       
         n 
        
       
      
        n\log n 
       
      
    nlogn的。
再来看性质 
     
      
       
       
         C 
        
       
      
        C 
       
      
    C,一条链状。
 长得与性质 
     
      
       
       
         A 
        
       
      
        A 
       
      
    A比较像,同样可以考虑二进制拆分。
 当我们询问按 
     
      
       
       
         k 
        
       
      
        k 
       
      
    k拆分询问后相当于可以得到这个点旁边的两个点在这一位上面的异或和。
 由于我们只要知道了一端的值就可以异或出另一端的值,我们可以考虑随便选一个点,算出与它相邻的点有哪些,然后就可以往两边推,求出所有点的异或值。
 这样,询问次数和修改次数仍然是 
     
      
       
       
         O 
        
        
        
          ( 
         
        
          n 
         
        
          log 
         
        
           
         
        
          n 
         
        
          ) 
         
        
       
      
        O\left(n\log n\right) 
       
      
    O(nlogn)的。
性质 
      
       
        
        
          D 
         
        
       
         D 
        
       
     D,看起来没啥用,可以不管。
 现在我们可以考虑一下正解会是怎么做的。
 可以发现, 
     
      
       
       
         m 
        
       
      
        m 
       
      
    m的级别是与 
     
      
       
       
         n 
        
       
      
        n 
       
      
    n差不多的,这可以表明绝大多数点,与其相连的点是比较少的。
 这时候我们的 
     
      
       
       
         c 
        
       
         h 
        
       
         e 
        
       
         c 
        
       
         k 
        
       
      
        check 
       
      
    check操作就可以派上用场了,我们找到的边越多,就可以用 
     
      
       
       
         c 
        
       
         h 
        
       
         e 
        
       
         c 
        
       
         k 
        
       
      
        check 
       
      
    check操作找出越多的被填满了的点,之后剩下的点也就越少。
 如果我们先选出了一个尚未被填满的比较小的集合 
     
      
       
       
         S 
        
       
      
        S 
       
      
    S,那么在 
     
      
       
       
         U 
        
       
         − 
        
       
         S 
        
       
      
        U-S 
       
      
    U−S中,就有极大可能存在与它直接相连的并且该边尚未被确定点,并且这个点与 
     
      
       
       
         S 
        
       
      
        S 
       
      
    S尚未被确定边连接数量为奇数的概率也相当高。
 那么我们可以考虑先判断出有哪些点符合我们上面所说的情况,再通过上面性质 
     
      
       
       
         B 
        
       
      
        B 
       
      
    B的方法二分出与它相连接的点。
 等到最后剩下的点数足够少了再用最开始暴力的方法,一下子求出剩下的所有边。
 这样做的话询问次数是 
     
      
       
       
         O 
        
        
        
          ( 
         
         
          
          
            n 
           
          
            2 
           
          
         
           B 
          
         
        
          + 
         
        
          m 
         
        
          log 
         
        
           
         
        
          B 
         
        
          ) 
         
        
       
      
        O\left(\frac{n^2}{B}+m\log B\right) 
       
      
    O(Bn2+mlogB)的,修改次数是 
     
      
       
       
         O 
        
        
        
          ( 
         
        
          n 
         
        
          + 
         
        
          B 
         
        
          log 
         
        
           
         
        
          B 
         
        
          ) 
         
        
       
      
        O\left(n+B\log B\right) 
       
      
    O(n+BlogB)的。
 根据情况合理规划 
     
      
       
       
         B 
        
       
      
        B 
       
      
    B的取值大概就可以过了。
懒得算时间复杂度了。
 由于不同的部分分限制不同,所以需要对许多部分分类讨论。
源码
//void modify(int x);
//int query(int x);
//void report(int x,int y);
//int check(int x);
#include "explore.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
#define MAXN 200005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lowbit(x) (x&-x)
int a[MAXN],col[MAXN],sta[MAXN],stak,st[MAXN],stk;
pii rg[MAXN];bool ful[MAXN],asked[MAXN];
vector<int>vec[MAXN],G[MAXN];
void explore(int n,int m){
    if(n<=500){
        for(int i=0;i<n-1;i++){
            modify(i);
            for(int j=i+1;j<n;j++)
                if(query(j)^col[j])
                    col[j]^=1,report(i,j);
        }
        return ;
    }
    if(n%10==8){
        for(int i=0;(1<<i)<n;i++){
            for(int j=0;j<n;j++)if(j&(1<<i))modify(j);
            for(int j=0;j<n;j++){
                int tmp=query(j);
                if((tmp!=col[j])^((j>>i)&1))a[j]^=(1<<i);
                col[j]=tmp;
            }
        }
        for(int i=0;i<n;i++)if(i<a[i])report(i,a[i]);
        return ;
    }
    if(n%10==7){
        for(int i=1;i<n;i++)rg[i]=mkpr(0,i-1);bool rev=0;
        while(1){
            bool flag=0;rev^=1;
            for(int j=1;j<n;j++)if(rg[j].fir<rg[j].sec){
                int mid=rg[j].fir+rg[j].sec>>1;
                vec[mid].pb(j);flag=1;
            }
            if(!flag)break;
            if(rev)for(int j=0;j<n;j++){
                int siz=vec[j].size();modify(j);
                for(int k=0;k<siz;k++){
                    int x=vec[j][k],tmp=query(x);
                    if(!tmp)rg[x].fir=j+1;else rg[x].sec=j;
                }
                vec[j].clear();
            }
            else for(int j=n-1;j>=0;j--){
                int siz=vec[j].size();
                for(int k=0;k<siz;k++){
                    int x=vec[j][k],tmp=query(x);
                    if(!tmp)rg[x].fir=j+1;else rg[x].sec=j;
                }
                vec[j].clear();modify(j);
            }
        }
        for(int i=1;i<n;i++)report(rg[i].fir,i);
        return ;
    }
    int num=0,up=(m>=50000?1000:100);
    while(num<m){
        stk=0;for(int i=0;i<n;i++)if(!ful[i])st[++stk]=i;
        if(stk<=up){
            for(int i=1;i<=stk;i++)col[st[i]]=query(st[i]);
            for(int i=1;i<=stk;i++){
                int x=st[i],siz=G[x].size();modify(x);
                for(int j=0;j<siz;j++)col[G[x][j]]^=1,asked[G[x][j]]=1;
                for(int j=i+1;j<=stk;j++)
                    if(!asked[st[j]]&&query(st[j])!=col[st[j]])
                        col[st[j]]^=1,report(x,st[j]);
                for(int j=0;j<siz;j++)asked[G[x][j]]=0;
            }
            return ;
        }
        random_shuffle(st+1,st+stk+1);
        int B=(stk>=100000?max(1,stk/10):(int)(sqrt(stk)*log(stk)));
        for(int i=B+1;i<=stk;i++)col[st[i]]=query(st[i]);
        for(int i=1;i<=B;i++){
            int x=st[i],siz=G[x].size();modify(x);
            for(int j=0;j<siz;j++)col[G[x][j]]^=1;
        }
        for(int i=B+1;i<=stk;i++)if(col[st[i]]^query(st[i]))
            rg[st[i]]=mkpr(1,B),asked[st[i]]=1,col[st[i]]^=1;
        bool rev=0;
        while(1){
            bool flag=0;rev^=1;
            for(int i=B+1;i<=stk;i++)
                if(asked[st[i]]&&rg[st[i]].fir<rg[st[i]].sec){
                    int mid=rg[st[i]].fir+rg[st[i]].sec>>1;
                    vec[st[mid]].pb(st[i]);flag=1;
                }
            if(!flag)break;
            if(rev)for(int i=1;i<=B;i++){
                int x=st[i],siz=G[x].size();modify(x);
                for(int j=0;j<siz;j++)col[G[x][j]]^=1;siz=vec[x].size();
                for(int j=0;j<siz;j++){
                    int y=vec[x][j];
                    if(query(y)==col[y])rg[y].fir=i+1;
                    else rg[y].sec=i;
                }
                vec[x].clear();
            }
            else for(int i=B;i>0;i--){
                int x=st[i],siz=vec[x].size();
                for(int j=0;j<siz;j++){
                    int y=vec[x][j];
                    if(query(y)==col[y])rg[y].fir=i+1;
                    else rg[y].sec=i;
                }
                vec[x].clear();modify(x);siz=G[x].size();
                for(int j=0;j<siz;j++)col[G[x][j]]^=1;
            }
        }
        for(int i=B+1;i<=stk;i++)if(asked[st[i]]){
            int x=st[rg[st[i]].fir],y=st[i];report(x,y);
            G[x].pb(y);G[y].pb(x);num++;asked[st[i]]=0;
            if(check(x))ful[x]=1;
            if(check(y))ful[y]=1;
        }
    }
}
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号