• P5795 [THUSC2015]异或运算


    题意描述:

    洛谷

    给你长度分别为 (n,m) 的两个数组 (x,y), 定义 $A_{ij} = x_i xor y_j $ ,有 (q) 组询问,每次询问你一个矩形区域 (iin [u,d], yin [l,r])(k) 大的数是多少。

    数据范围: (nleq 1000,mleq 3e5, q leq 500)

    做法一:

    复杂度 (O(31nqlog_{ans})) 。常数有点(特别)大,在你谷吸氧能拿到 (90) 分。

    首先第 (k) 大我们很容易想到二分答案。然后就变为了 (x_i xor y_j leq mid) 的数对有多少。

    考虑到 (n) 的范围比较小,所以我们来枚举每一个 (i in [u,d]) ,统计一下对答案的贡献。

    问题就转化为了对于一个数 (x),问你 (x xor y_j leq mid)(j) 的数量。

    (y) 建立一棵可持久化 ( tire) 树,然后在 (tire) 树上二分即可。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N = 3e5+10;
    int n,m,q,u,d,l,r,tot = 1,k;
    int x[N],y[N],rt[N],siz[50000010],tr[50000010][2];
    inline int read()
    {
    	int s = 0,w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    	return s * w;
    }
    void insert(int p,int q,int x)//可持久化tire树
    {
    	for(int i = 30; i >= 0; i--)
    	{
    		int c = (x>>i) & 1;
    		siz[p] = siz[q] + 1;
    		tr[p][c] = ++tot;
    		tr[p][!c] = tr[q][!c];
    		p = tr[p][c];
    		q = tr[q][c];
    	}
    	siz[p] = siz[q] + 1;
    }
    int query(int p,int q,int a,int b)//tire树上二分
    {
    	int res = 0;
    	for(int i = 30; i >= 1; i--)
    	{
    		int A = (a>>i) & 1;
    		int B = (b>>i) & 1;
    		if(B == 1)
    		{
    			res += siz[tr[p][A]] - siz[tr[q][A]];
    			p = tr[p][!A];
    			q = tr[q][!A];
    		}
    		else 
    		{
    			p = tr[p][A];
    			q = tr[q][A];
    		}
    	}
    	int A = a & 1, B = b & 1;
    	if(B == 1) res += siz[tr[p][!A]] - siz[tr[q][!A]];
    	res += siz[tr[p][A]] - siz[tr[q][A]];
    	return res;
    }
    bool judge(int mid)
    {
    	int res = 0;
    	for(int i = u; i <= d; i++) res += query(rt[r],rt[l-1],x[i],mid);
    	return (d-u+1)*(r-l+1)-res + 1 <= k;
    }
    signed main()
    {
    	n = read(); m = read();
    	for(int i = 1; i <= n; i++) x[i] = read();
    	for(int i = 1; i <= m; i++) y[i] = read();
    	q = read(); 
    	rt[0] = ++tot; insert(rt[0],0,0);
    	for(int i = 1; i <= m; i++) rt[i] = ++tot, insert(rt[i],rt[i-1],y[i]);
    	for(int i = 1; i <= q; i++)
    	{
    		u = read(); d = read(); l = read(); r = read(); k = read();
    		int L = 0, R = (1LL<<31)-1, ans = 0;
    		while(L <= R)//二分答案
    		{
    			int mid = (L + R)>>1;
    			if(judge(mid))
    			{
    				R = mid - 1;
    				ans = mid;
    			}
    			else L = mid + 1;
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    做法2:

    复杂度 (O(31nq)) ,跑的比上面的快多了。

    实际上我们是没有必要二分答案的,因为对于每一位来说,他的贡献是独立的,不管你后面怎么填,你这一位为 (1) 的数肯定比你这一位为 (0) 的数要大。

    还是枚举每一个 (iin [u,d]) ,对 (y) 建立一棵可持久化 (tire) 树,然后进行按位考虑,统计一下异或起来这一位为 (0) 的数量 (num_0),然后根据 (num_0)(k) 的关系判断出 (res) 这一位填 (0) 还是填 (1) 以及走那条边即可。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N = 3e5+10;
    int n,m,q,u,d,l,r,tot = 1,k;
    int x[N],y[N],rt[N],siz[40000010],tr[40000010][2],p[40000010][2];
    inline int read()
    {
    	int s = 0,w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    	return s * w;
    }
    void insert(int p,int q,int x)
    {
    	for(int i = 30; i >= 0; i--)
    	{
    		int c = (x>>i) & 1;
    		siz[p] = siz[q] + 1;
    		tr[p][c] = ++tot;
    		tr[p][!c] = tr[q][!c];
    		p = tr[p][c];
    		q = tr[q][c];
    	}
    	siz[p] = siz[q] + 1;
    }
    int query(int u,int d,int l,int r,int k)
    {
    	k = (d-u+1) * (r-l+1) - k + 1;
    	for(int i = u; i <= d; i++) 
    	{
    		p[i][0] = rt[l-1];
    		p[i][1] = rt[r]; 
    	}
    	int res = 0;
    	for(int i = 30; i >= 0; i--)
    	{
    		int cnt = 0;
    		for(int j = u; j <= d; j++)
    		{
    			int c = (x[j]>>i)&1;
    			int p1 = tr[p[j][0]][c], p2 = tr[p[j][1]][c];
    			cnt += siz[p2] - siz[p1];//统计为 0 的个数
    		}
    		int tag = 0;
    		if(k > cnt) k -= cnt, tag = 1;//如果这一位0的个数比k少,说明这一位肯定为1,反之为0
    		res = (res<<1) + tag;//计算第k大的数的数值
    		for(int j = u; j <= d; j++)
    		{
    			int c = (x[j]>>i) & 1;
    			if(tag == 1)
    			{
    				p[j][0] = tr[p[j][0]][c^1];//res这一位为1的话就要走 c^1 这条边
    				p[j][1] = tr[p[j][1]][c^1];
    			}
    			else
    			{
    				p[j][0] = tr[p[j][0]][c];//反之走 c这条边
    				p[j][1] = tr[p[j][1]][c];
    			}
    		}    
    	}
    	return res;
    }
    signed main()
    {
    	n = read(); m = read();
    	for(int i = 1; i <= n; i++) x[i] = read();
    	for(int i = 1; i <= m; i++) y[i] = read();
    	q = read(); 
    	rt[0] = ++tot; insert(rt[0],0,0);
    	for(int i = 1; i <= m; i++) rt[i] = ++tot, insert(rt[i],rt[i-1],y[i]);
    	for(int i = 1; i <= q; i++)
    	{
    		u = read(); d = read(); l = read(); r = read(); k = read();
    		printf("%lld
    ",query(u,d,l,r,k));
    	}
    	return 0;
    }
    
  • 相关阅读:
    大道至简 读后感01
    《人月神话》读后感
    水王继续
    软工大作业DB天气项目风险评估
    自我调查 使用输入法
    课堂练习之找“水王”
    浪潮之巅阅读笔记之三
    浪潮之巅阅读笔记之二
    浪潮之巅阅读笔记之一
    课程改进意见
  • 原文地址:https://www.cnblogs.com/genshy/p/14379763.html
Copyright © 2020-2023  润新知