• JZOJ 6829. 【2020.10.25提高组模拟】异或(DP+线段树)


    JZOJ 6829. 【2020.10.25提高组模拟】异或

    题目大意

    • 给出一个长度为 N N N的序列 A A A,求元素两两异或值不小于 X X X的非空子序列个数。
    • N ≤ 300000 , 0 ≤ A i , X < 2 60 Nleq300000,0leq A_i,X< 2^{60} N3000000Ai,X<260.

    题解

    • 一开始看到“子序列”困扰了很久,但会发现,题目又要求子序列两两满足某种要求,所以就和序列顺序无关了,那么可以把“子序列”看做是“子集”,
    • 什么样的子集满足条件呢?能否简化题目限制?
    • 结论是两两异或最小值一定会出现在大小每相邻两数异或值当中,怎么证明?
    • 任意递增的三个数,按二进制拆位来看后,总有某一位上的三个数不全相等,
    • 满足三个数不全相等且最高的一位所产生的贡献,足以覆盖过其他位的贡献,所以只用考虑这一位,
    • 只有 0 , 0 , 1 0,0,1 0,0,1 0 , 1 , 1 0,1,1 0,1,1两种情况,而显然它们都满足上面的结论。
    • 那么可以把 A A A排序后,设 f i f_i fi为选到第 i i i个数且选了第 i i i个数的方案数,则 f i = 1 + ∑ A i ⨁ A j ≥ X f j f_i=1+sum_{A_iigoplus A_jgeq X}f_j fi=1+AiAjXfj,然而这样复杂度显然过不去。
    • 考虑用数据结构优化 A i ⨁ A j ≥ X A_iigoplus A_jgeq X AiAjX的判断,可以把每个 f i f_i fi挂在线段树上,这里注意线段树的区间必须是 [ 0 , 2 k ) [0,2^k) [0,2k),这样左右儿子可以分别对应某一位的 0 / 1 0/1 0/1
    • 插入就直接插入到对应的位置,
    • 查询的话,每到一位上,如果 X X X的这一位为 1 1 1,那异或后是 0 0 0的子树上就都不满足,直接递归另一边;如果 X X X的这一位为 0 0 0,那异或后是 1 1 1的子树上就都满足,可以直接加上,也只用递归另一边。

    代码

    #include<cstdio> 
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define ll long long
    #define N 300010
    #define md 998244353
    ll X, a[N], F[N];
    int len = 1;
    struct {
    	ll s = 0;
    	int l = 0, r = 0;
    }f[N * 60];
    ll find(int v, ll t, ll x, ll s) {
    	if(s >= X) return f[v].s;
    	ll sum = 0;
    	if(f[v].l && ((X & t) == 0 || (x & t) != 0)) (sum += find(f[v].l, t / 2, x, s + ((x & t) ^ 0))) %= md;
    	if(f[v].r && ((X & t) == 0 || ((x & t) ^ t) != 0)) (sum += find(f[v].r, t / 2, x, s + ((x & t) ^ t))) %= md;
    	return sum;
    }
    void add(int v, ll t, ll x, ll c) {
    	if(t == 0) (f[v].s += c) %= md;
    	else {
    		if(x & t) {
    			if(!f[v].r) f[v].r = ++len;
    			add(f[v].r, t / 2, x, c);
    		}
    		else {
    			if(!f[v].l) f[v].l = ++len;
    			add(f[v].l, t / 2, x, c);
    		}
    		f[v].s = 0;
    		if(f[v].l) (f[v].s += f[f[v].l].s) %= md;
    		if(f[v].r) (f[v].s += f[f[v].r].s) %= md;
    	}
    }
    int main() {
    	int n, i;
    	scanf("%d%lld", &n, &X);
    	for(i = 1; i <= n; i++) scanf("%lld", &a[i]);
    	sort(a + 1, a + n + 1);
    	ll ans = 0;
    	for(i = 1; i <= n; i++) {
    		F[i] = find(1, (1ll << 59), a[i], 0) + 1;
    		add(1, (1ll << 59), a[i], F[i]);
    		(ans += F[i]) %= md;
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }
    
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    明年,我们依然年轻
    总有些东西会如台风一样的来
    ora00257错误处理方法
    ORACLE登录错误的解决方法
    C#中public new void add()的new在这里的意义
    Oracle Form Builder配置问题的一些总结
    作为程序员,你应该知道的职场晋升之路
    ORA01034错误的解决方法
    【转】JQUERY刷新页面
    【转】对C# 中堆栈,堆,值类型,引用类型的理解
  • 原文地址:https://www.cnblogs.com/LZA119/p/13910013.html
Copyright © 2020-2023  润新知