CF1725D Deducing Sortability

有一个数组 \(A\),可以在上面进行任意次操作。每次操作选择一个位置,把它减去 \(2^c\) 后再乘 \(2\)(其中 \(c\) 是已经在这个位置上操作过的操作数)。每次操作之后,数组中的所有元素必须是正整数。如果可以将它变得单调上升,那么它就是一个好的数组。

现在需要构造长为 \(n\) 的一个好的数组,使得 \(\sum A_i\) 最小,在此基础上使得它的字典序最小,输出 \(\sum A_i\),并 \(q\) 次询问某个位置上的值,保证位置升序给出。

\(n\le10^9,q\le\min(n,10^5)\)


第一道没看题解写出来的 CF 2000+?(但是队友也给了我很多提示,所以应该不算(

首先,我们考虑一个数经过若干次后可以变成什么,假设原来是 \(x\),那么经过 \(k\) 次操作后,\(x\) 会变成 \(2^k(x-k)\)

接着,我们反过来考虑可以操作成 \(y\) 的数中最小的数是什么。

显然只要 \(2^k|y\),那么 \(\dfrac{y}{2^k}+k\) 就可以被操作成 \(y\)

我们发现,只要 \(y\) 是一个偶数,那么有 \(y\ge2\),此时 \(y-\dfrac{y}{2}\ge1\),那么我们贪心地取尽可能大的 \(k\) 是不劣的。

因此,在下文中,我们定义 \(f(y)=\dfrac{y}{2^k}+k\),其中 \(k\)\(y\) 二进制低位连续 \(0\) 的数量。

考虑回原问题,我们要操作出一个单调上升的 \(A\),这意味着 \(A\) 相邻两项必不能相同。

那么我们此时选择 \(f(A_i)\) 作为初始时原位置上的数必定是最优的。

因此我们可以选择考虑最终的序列 \(B\),答案就是 \(\sum f(B_i)\)

题目中的要求是长至少为 \(n\),因此最优策略就是贪心地每次选择 \(f(x)\) 最小的还未出现过的 \(x\) 插入到 \(A\) 里。

接下来我们考虑对于一个数 \(x\),有多少个数 \(y\)\(f(y)=x\),由于 \(f(y)=\dfrac{y}{2^k}+k\),我们令 \(z=\dfrac{y}{2^k}\)

枚举 \(1\le z\le x\),假如 \(z\) 是一个偶数,那么不符合 \(k\)\(y\) 二进制低位连续 \(0\) 的数量的定义,直接舍去。

剩余的所有奇数 \(z\) 都是合法的,因此我们可以得知,一共存在 \(\lceil\dfrac{x}{2}\rceil\)\(y\) 使得 \(f(y)=x\)

那么第一问就可以简单解决了,直接从小到大枚举 \(x\),最多只有 \(\sqrt{n}\)\(x\)

接下来我们考虑第二问,要求出所有数里面的第 \(k\) 大,第一眼看上去较困难,因为我们根本无法记下这么大和这么多的数。

但是我们发现每个数都能被表示为 \(2^ab\) 的形式,我们考虑对所有数取对数,那么这个数就可以被表示为 \(a+\log_2b\)

\(\log_2b\) 是不好计算的,因为这个东西是小数,我们考虑枚举 \(b\),考虑所有的 \(a+log_2b\) 的数。

显然,对于所有 \(b \le x\)\(x\) 都可以贡献到 \(b\),我们把 \(log_2b\) 的整数部分和小数部分分离,那么所有的 \(a+\log_2b\) 就可以被表示为 \(\log_2b\) 的小数部分再加上一个区间里整数的形式。

那么我们首先把所有 \(\log_2b\) 的小数部分预处理出来并离散化,然后从小到大对整数部分扫描线,然后用数据结构维护插入删除第 \(k\) 大即可。

我用的是树状数组,但是不知道为何仍然常数较大()

#pragma GCC optimize("Ofast", "inline", "no-stack-protector")
#include <algorithm>
#include <iostream>
#include <string.h>
#include <math.h>
#include <queue>
using std::cin, std::cout, std::cerr;
const int N = 1e6 + 7, Lg = std::__lg(N) + 1, M = 1 << Lg;
typedef long long i64;

#define R(i,a,b) for(int i(a);i<=(b);++i)
typedef double unit;

struct bit {
	int tr[M], cnt;
	void add(int x, int y) {
		cnt += y;
		for(; x < M; x += x & -x)
			tr[x] += y;
	}
	int qry(int x) {
		int y = 0;
		for(; x; x -= x & -x)
			y += tr[x];
		return y;
	}
	int kth(int k) {
		int p = M;
		for(int i = Lg-1; ~i; --i)
			if(tr[p - (1 << i)] >= k)
				p -= (1 << i);
		else k -= tr[p - (1 << i)];
		return p;
	}
} T;

int n, q;

unit val[N]; int offset[N], rnk[N], fr[N];
std::vector<unit> vals;
int qry[N], ans[N];
std::vector<int> st[N], ed[N];

int main() {
	cin >> n >> q;
	int i = 1;
	i64 _ans = 0, t = 0;
	for(; t + (i + 1) / 2 < n; ++i) {
		t += (i + 1) / 2, _ans += i * ((i + 1) / 2);
	}
	_ans += i * (n - t);
	cout << _ans << "\n";
	
	for(int j = 1; j <= (i + 1) / 2; ++j) {
		int w = 2 * j - 1;
		val[j] = log2(w);
		while(val[j] >= 1) val[j] -= 1, ++offset[j];
		offset[j] -= w;
		vals.push_back(val[j]);
	}
	
	std::sort(vals.begin(), vals.end());
	vals.erase(std::unique(vals.begin(), vals.end()), vals.end());
	
	for(int j = 1; j <= (i + 1) / 2; ++j) {
		rnk[j] = std::lower_bound(vals.begin(), vals.end(), val[j]) - vals.begin() + 1;
		fr[rnk[j]] = j;
	}
	
	int Rp = 0;
	for(int j = 1; j <= (i + 1) / 2; ++j) {
		int l = 2 * j - 1, r = i - 1;
		if(j <= (n - t)) ++r;
		Rp = std::max(Rp, r + offset[j]);
		st[l+offset[j]].push_back(rnk[j]);
		ed[r+offset[j]+1].push_back(rnk[j]);
	}
	
	R(j, 1, q) cin >> qry[j];
	
	int Kt = 0, pt = 1;
	for(int j = 0; j <= Rp; ++j) {
		for(int& x: st[j]) T.add(x, 1);
		for(int& x: ed[j]) T.add(x, -1);
		int K = T.cnt;
		while(pt <= q && qry[pt] <= Kt + K) {
			int w = T.kth(qry[pt] - Kt);
			ans[pt++] = j - offset[fr[w]];
		}
		Kt += K;
	}
	
	R(j, 1, q) cout << ans[j] << "\n";
}
posted @ 2025-02-27 16:26  CuteNess  阅读(36)  评论(0)    收藏  举报