• 2020-11-21 f


    题意:给定一个长度为 (n) 的序列 (A)(A_i in [0, 2 ^ k))。定义 (f(x))(A_1) ^ (x)(A_2) ^ (x cdots) (A_n) ^ (x) 这个序列的逆序对数量。现在将 (x in [0, 2 ^ k)) 按照 (f(x)) 为第一关键字,(x) 为第二关键字从小到大排序,求排名为 (p)(f(x))(x)

    (n le 5 imes 10 ^ 5, 0 le k le 30, 1 le p le 2 ^ k)


    简要题解如下:

    1. 考虑点对 ((i, j)(i < j)) 对每个 (f(x)) 的贡献。

    2. 不难发现,找到 (a_i, a_j) 最高的不相同的位置 (u) 那么 (a_i, a_j) ^ (x) 后的大小关系只于 (u) 这一位的选择有关。

    3. (a_i < a_j) 那么当且仅当 (u) 这位为 (1) 会造成贡献;否则 (u) 这位为 (0) 会造成贡献。

    4. 注意到枚举点对是 (mathcal{O(n ^ 2)}) 级别的,考虑优化这个枚举的过程。

    5. 可以发现因为本质上是点对对答案造成的贡献,可以使用类似于 ( m CDQ) 分治的做法来优化这个流程。

    6. 我们将在从高往低 (u) 这个位上不同的数分为两个集合,因为数组是按照排列顺序来的,于是可以 (mathcal{O(n)}) 地计算出这一位的贡献。然后递归处理两个集合,复杂度 (mathcal{O(nk)})

    7. 接下来考虑一个经典转化:二分答案,统计小于小于等于 ((f(x), x)) 的数量个数。

    8. 统计时折半搜索,然后使用双指针优化即可减小一半的指数。

    复杂度 (mathcal{O(k2 ^ {k / 2} + nk)})

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    const int N = 5e5 + 5;
    const int M = 30 + 5;
    struct node { int x, y;} L[(1 << (M / 2))], R[(1 << (M / 2))];
    int n, k, p, lx, rx, val, a[N], f[2][M];
    int read() {
    	char c; int x = 0, f = 1;
    	c = getchar();
    	while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
    	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return x * f;
    }
    void solve(vector <int> G, int k) {
    	if(k < 0 || !G.size()) return ;
    	vector <int> L, R; int lx = 0, rx = 0;
    	for (int i = 0; i < G.size(); ++i) {
    		if((G[i] >> k) & 1) f[1][k] += lx, ++rx, R.push_back(G[i]);
    		else f[0][k] += rx, ++lx, L.push_back(G[i]);
    	}
    	solve(L, k - 1), solve(R, k - 1);
    }
    bool cmp(node a, node b) { return a.x == b.x ? a.y < b.y : a.x < b.x;} 
    int check(int val, int x) {
    	int ans = 0, j = rx;
    	rep(i, 1, lx) {
    		for (; R[j].x + L[i].x > val && j; --j) ;
    		for (; R[j].x + L[i].x == val && R[j].y + L[i].y > x && j; --j) ;
    		ans += j;
    	}
    	return ans;
    }
    signed main () {
    	freopen ("f.in", "r", stdin);
    	freopen ("f.out", "w", stdout);
    	n = read(), k = read(), p = read();
    	rep(i, 1, n) a[i] = read();
    	vector <int> G; rep(i, 1, n) G.push_back(a[i]);
    	solve(G, k - 1);
    	lx = (1 << (k / 2)), rx = (1 << (k - k / 2));
    	rep(i, 0, lx - 1) L[i + 1].y = i * rx;
    	rep(i, 0, rx - 1) R[i + 1].y = i;
    	rep(i, 0, lx - 1) rep(j, 0, k / 2 - 1) L[i + 1].x += f[(i >> j) & 1][j + k - k / 2];
    	rep(i, 0, rx - 1) rep(j, 0, k - k / 2 - 1) R[i + 1].x += f[(i >> j) & 1][j];
    	sort(L + 1, L + lx + 1, cmp), sort(R + 1, R + rx + 1, cmp);
    	int l = 0, r = n * n / 2;
    	while (l < r) {
    		int Mid = (l + r) / 2;
    		if(check(Mid, 1 << k) >= p) r = Mid;
    		else l = Mid + 1;
    	}
    	val = r, l = 0, r = (1 << k) - 1;
    	while (l < r) {
    		int Mid = (l + r) / 2;
    		if(check(val, Mid) >= p) r = Mid;
    		else l = Mid + 1; 
    	}
    	printf("%lld %lld", val, r);
    	return 0;
    }
    

    首先发现点对对每位之间的贡献是独立的是非常重要的

    其次需要意识到本质上是点对的贡献,通常使用分治解决。

    注意查排名或字典序的套路:

    1. 二分答案转化为查询小于等于答案的数量个数

    2. 按位确定答案,同样转化为统计某一个前缀下后缀随意的数量

    折半搜索通常解决一类选择没有关联性,两边可合并的问题,数据范围出现 (30, 40) 尤其要注意。

  • 相关阅读:
    解决:打开OleView报错 dllregisterserver in iviewers failed
    VS中查看/修改Dialog控件TAB顺序的方法
    如何调试DLL组件
    【转】解决:fatal error C1083: 无法打开预编译头文件
    Oracle ROWNUM用法和分页查询总结
    ORACLE的rownum用法讲解
    LINUX重启MYSQL的命令
    电脑资源管理器被关闭怎么启动
    Microsoft Active Directory(LDAP)连接常见错误代码
    IntelliJ IDEA 常用设置
  • 原文地址:https://www.cnblogs.com/Go7338395/p/14016996.html
Copyright © 2020-2023  润新知