• @51nod



    @description@

    从左到右一共 n 个数,数字下标从 1 到 n 编号。

    一共 m 次询问,每次询问是否能从第 L 个到第 R 个数中(包括第 L 个和第 R 个数)选出一些数使得他们异或为 K。

    input
    第一行一个整数 n (0<n<=500,000)。
    第二行 n 个整数,0<每个数<2^30。
    第三行一个数 m,表示询问次数 (0<m<=500,000)。
    接下来 m 行每行三个数,L, R, K (0<L<=R<=n,0<K<2^30)。

    output
    M 行,每行为 YES 或 NO

    sample input
    5
    1 1 2 4 6
    3
    1 2 1
    2 4 8
    3 5 7
    sample output
    YES
    NO
    NO

    @solution@

    能否选出一些数使这些数的异或和为 K。不难想到使用线性基来解决。

    如果在题目给定的数据范围下想要直接构造出区间 [L, R] 的线性基实际上并不容易。
    带根号的比如莫队、分块肯定跑不动。在线使用线段树 O(nlog^3 n),离线用分治可以做到 O(nlog^2 n),能过一些点但是仍然不够优秀。
    关键在于我们想要构造出 [L, R] 的线性基,涉及到会合并两个线性基的操作,而这个操作复杂度始终是 log^2 n 降不下来。

    不妨换个角度:我们给定 R 与 K,可以发现 L 越往左能异或出的值越多。于是我们可以尝试去找第一个可以异或出 K 的 L',然后再比较 L 与 L' 的大小判断合法性。
    我们尝试对于每一个 R 维护出最靠近 R 的若干线性无关的数构成的线性基。这个思路类似于 bzoj3514 的解题思路。

    具体操作来说,我们从左往右扫,由 R-1 的线性基递推出 R 的线性基。
    我们在线性基从高位往低位。如果当前的数在第 i 位为 1,再看线性基的第 i 位是否已经有数了。如果没有数则直接放进去,如果有数则比较当前的数和线性基里的数哪一个更靠近 R,将更靠近的放入线性基,另一个继续执行插入操作。
    有点像冒泡排序,更靠近的 R 的数将其他数挤了出去。

    同时,我们可以发现线性基中的高位取了尽量靠近 R 的数。
    所以当我们尝试异或出 K 时,如果遇到某一位的数的位置小于 L,并且这一位数是我们在异或出 K 时所需要的,可以直接判定为“NO”。
    说实话这里严格证明起来很麻烦。我能力有限,无法找到一个较为简洁的该算法正确性证明。
    毕竟线性基也是线性代数啊。怎么可能会很简单。

    @accepted code@

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int MAXN = 500000;
    int read() {
    	int x = 0; char ch = getchar();
    	while( ch > '9' || ch < '0' ) ch = getchar();
    	while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
    	return x;
    }
    void insert(int *pos, int *b, int x, int p) {
    	for(int i=29;i>=0;i--) {
    		if( x & (1<<i) ) {
    			if( b[i] == 0 ) {
    				b[i] = x;
    				pos[i] = p;
    			}
    			else if( pos[i] < p ) {
    				swap(pos[i], p);
    				swap(b[i], x);
    			}
    			x ^= b[i];
    		}
    	}
    }
    bool search(int *pos, int *b, int x, int p) {
    	for(int i=29;i>=0;i--) {
    		if( x & (1<<i) ) {
    			if( b[i] == 0 || pos[i] < p )
    				return false;
    			x ^= b[i];
    		}
    	}
    	return true;
    }
    int pos[MAXN + 5][30], b[MAXN + 5][30];
    int main() {
    	int n, m; n = read();
    	for(int i=1;i<=n;i++) {
    		int x = read();
    		for(int j=29;j>=0;j--)
    			pos[i][j] = pos[i-1][j], b[i][j] = b[i-1][j];
    		insert(pos[i], b[i], x, i);
    	}
    	m = read();
    	for(int i=1;i<=m;i++) {
    		int L = read(), R = read(), K = read();
    		puts(search(pos[R], b[R], K, L) ? "YES" : "NO");
    	}
    }
    

    @details@

    好久没复习线性基了。。。写这个题的时候搞出了一个假的线性基。。。于是 WA 的很惨烈。。。

    线性基的插入可以类比为动态加入方程的高斯消元。不过因为我们需要构造出特殊的线性基(即要求线性基的第 i 位的数二进制下最高位的 1 在第 i 位。。。好像有些绕口),所以写高斯消元无法满足这些性质。
    另外线性基还可以再特殊一点(类比高斯消元化至最简形式),即要求线性基第 i 位上如果有数,则其他位置的二进制下第 i 位上为 0。

    好像很多人都是离线做的,不过这道题可以通过存储每个位置的线性基做到在线来着。

  • 相关阅读:
    全选、反选和不选
    树状图
    年月日选择器
    细谈HTML5
    再谈HTML
    FlashFXP 破解代码
    文件上传示例
    PHP聊天室框架
    JS判断是否来自手机移动端的访问,并跳转
    JQUERY知识总结
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11123375.html
Copyright © 2020-2023  润新知