zoukankan      html  css  js  c++  java
  • RDay1-Problem 3 C

    题目描述
    初始给定n个卡片拍成一排,其中第i个卡片上的数为x[i]。
    有q个询问,每次询问给定L和R表示询问区间[L,R]内的卡片所有出现了偶数次的数的异或和是多少。

    输入
    输入文件C.in
    输入一行两个整数n,q。
    第二行n个整数,第i个数为x[i]。
    接下来q行,每行两个整数L和R,表示询问的区间。

    输出
    输出文件C.out
    输出q行,其中第i行表示第i次询问的区间出现偶数次的数的异或和。

    样例输入

    3 1
    3 7 8
    1 3
    

    样例输出

    0
    

    【样例输入2】

    7 5 
    1 2 1 3 3 2 3 
    4 7 
    4 5 
    1 3 
    1 7 
    1 5 
    

    【样例输出2】

    0 
    3 
    1 
    3 
    2 
    

    【数据范围】
    对于20%的数据,$ n , q le 10^3 $

    对于另外20%的数据,$ n , q le 5 imes 10^4 , a_i le 10 $

    对于100%的数据, $ n , q le 10^6 , 0 le a[i] le 10^9 $

    这道毒瘤题.....我->TM->根本->不会
    但是我会四十分啊.......
    四十分就很简单了,首先,对于前 $ 20 % $ 的数据,直接暴力模拟即可,毫无难度可言
    另外 $ 20 % $ 的数据,需要稍微动一点脑子,因为它具有特殊性质:$ a_i le 10 $
    所以可以在前缀和的基础上加一维来作为桶统计出现次数,然后就是判断了,也很简单
    虽然写的是40分,但是我得了45分,也算是意外惊喜吧.
    四十分代码如下:

    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    #include <cstdio>
    #define max(a,b) (a > b ? a : b)
    #define Noip2018RpINF return 0 
    
    const int N = 5e4 + 5 ;
    
    int n,v[N],q,tra[N],maxi,ans;
    int sum_ap[N][15];
    
    inline int read(){
    	int x = 0 , f = 1 ;char ch = getchar ();
    	while ( ch < '0' || ch > '9' ){if ( ch == '-' ) f = - 1 ;ch = getchar () ;}
    	while ( ch >= '0' && ch <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( ch ^ 48 ) ;ch = getchar ();}
    	return f * x ;
    }
    
    int main(){
    	n = read () , q = read() ;
    	for (int i = 1 ; i <= n ; ++ i) v[i] = read () , maxi = max (maxi , v[i]) ;
    	if ( n <= 2000 ){
    		while (q--){
    			register int l = read () , r = read () ;
    			memset( tra , false , sizeof (tra) );maxi = - 1 ; ans = 0 ;
    			for (int i = l ; i <= r ; ++ i) ++ tra[ v [ i ] ] , maxi = max (maxi , v[i]);
    			for (int i = 1 ; i <= maxi ; ++i){
    				if( ( tra[i] & 1 ) || ! tra[i] ) continue ;
    				ans ^= i ;
    			}
    			printf("%d
    " , ans);
    		}
    	}else if(n <= 50000){
    		sum_ap[1][v[1]] = 1 ;
    		for (int i = 2 ; i <= n ; ++ i)
    			for(int j = 0 ; j <= maxi ; ++ j)
    				if( v[i] == j ) sum_ap[i][j] = sum_ap[i - 1][j] + 1 ;
    				else sum_ap[i][j] = sum_ap[i - 1][j] ;
    		while (q--){
    			register int  l = read () , r = read (); ans = 0 ;
    			for(int i = 1 ; i <= maxi ;++ i){
    				if( ! ( (sum_ap[r][i] - sum_ap[ l - 1 ][i]) & 1 ) && (sum_ap[r][i] - sum_ap[ l - 1 ][i]) )
    					ans ^= i ;
    			}
    			printf ("%d
    " , ans);
    		}
    	}
    	Noip2018RpINF;
    }
    

    通过我校 $ rjx 大佬 $ 的讲解,我成功学会了这道题的正解:树状数组+前缀异或和
    有一点点前置知识: $ a igoplus b = c $ 则 $ a = b igoplus c $
    我们用 $ a $ 表示区间内出现偶数次的数的异或和,用 $ b $ 表示区间内出现了奇数次的数的异或和,用 $ c $ 表示整个区间所有数字的异或和(每个数字只计算一次)
    那么,根据上面的性质,我们可以得到如下结论:
    $ a = b igoplus c $
    所以我们就考虑维护区间内出现奇数次的数的异或和以及整个区间所有不同的数字的异或和
    区间内出现奇数次的数的异或和显然可以使用前缀异或和来维护
    那么整个区间内所有不同的数的异或和呢?
    这里我们采用树状数组维护,开一个数组 $ pre_i $ 表示在 $ i $ 这个位置之前的最靠近 $ v_i $ 的数的位置
    另一个 $ map $ 表示 $ v_i $ 的下标,用来辅助构建 $ pre $ 数组
    然后,我们把所有的询问区间离线,按右端点升序排序,按升序排序的原因是我们从左向右推,这样可以避免推完之后再回去推一遍,从而保证单纯推的过程复杂度保持在 (Theta(n))
    之后,在向右推进的过程中,我们每遇到一个数,如果它没有前缀(即第一次出现,它对应的 $ pre $ 数组为空),那么就直接加入树状数组
    否则,现在树状数组中删除它的前缀,再把它加进去,这样,显然我们可以保证最后树状数组中有且仅有每个数字一个,最后我们查询树状数组,再把查询结果异或整段区间的异或和(即出现了奇数次的异或和)就得到该次询问的答案
    代码如下:

    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <map>
    #define lowbit(x) ( - x & x )
    
    using std::map;
    
    const int N = 1e6 + 5 ;
    
    struct querys{
    	int left , right , dir ;
    }qs[N];
    
    int n,q,v[N],pre[N];
    int ans[N],t[N],sum[N];
    map < int , int > vis ;
    
    inline void update(int dir , int val){
    	while(dir <= n){
    		t[dir] ^= val ;
    		dir += lowbit(dir);
    	}
    	return ;
    }
    
    inline int query(int dir){
    	register int res = 0 ;
    	while(dir){
    		res ^= t[dir] ;
    		dir -= lowbit(dir);
    	}
    	return res;
    }
    
    inline bool cmp(const querys & a ,const querys & b ){return a.right < b.right ;}
    
    int main(){
    	scanf("%d%d" , & n , & q );
    	for (int i = 1 ; i <= n ; ++ i){
    		scanf("%d" , & v[i] );
    		sum[i] = sum[i - 1] ^ v[i] ;
    		pre[i] = vis[ v[i] ] ; vis[ v[i] ] = i ;
    	}
    	for (int i = 1 ; i <= q ; ++ i)
    		scanf("%d%d" , & qs[i].left , & qs[i].right ) , qs[i].dir = i ;
    	std::sort( qs + 1 , qs + q + 1 , cmp );
    	register int now = 1 ;
    	for (int i = 1 ; i <= q ; ++ i){
    		while(now <= qs[i].right){
    			if( pre[now] ) update( pre[now] , v[now] );
    			update( now , v[now] ); ++ now ;
    		}
    		ans[qs[i].dir] = query( qs[i].right ) ^ query( qs[i].left - 1 ) ^ sum[ qs[i].right ] ^ sum[ qs[i].left - 1 ] ;
    	}
    	for (int i = 1 ; i <= q ; ++ i) printf("%d
    " , ans[i]);
    	return 0;
    }
    
    May you return with a young heart after years of fighting.
  • 相关阅读:
    单核时代,PHP之类多线程或者多进程的,是怎么处理并发的?是排队吗?
    高并发下的Node.js与负载均衡
    telnet 查看端口是否可访问
    同步与异步--
    函数式编程沉思录(草稿)
    面向状态机编程
    promise是有状态的moand
    异步链式编程—promise沉思录
    同步与异步
    网络编程释疑之:同步,异步,阻塞,非阻塞
  • 原文地址:https://www.cnblogs.com/Equinox-Flower/p/9896846.html
Copyright © 2011-2022 走看看