CF1436 E,F Codeforces Round #678 (Div. 2)

题目来源:Codeforces,#678,Codeforces Round #678 (Div. 2),CF1436;CF1436E Complicated Computations,CF1436F Sum Over Subsets。

CF1436E Complicated Computations

题目链接

从小到大依次枚举每个正整数,看能否作为答案。一个数 (x) 能作为答案的条件是:

  1. 比它小的值都不是答案。
  2. 不存在一个区间 ([l,r]),使得 ( ext{mex}(a_l,a_{l+1},dots,a_r) = x)

因为是从小到大枚举的,所以只要我们一找到答案就退出,那条件 1 就已经解决了。

考虑条件 2。如何判断是否存在一个区间,使得 ( ext{mex}(a_l,a_{l+1},dots,a_r) = x) 呢?

  • 首先,(x) 不能在这个区间里出现过。记 (x) 在整个序列里出现的位置分别为 (p_1,p_2,dots,p_k),这些点把整个序列划分为了不超过 (k+1) 个线段。那 ([l,r]) 一定被完全包含在某个线段中。
  • 其次,所有小于 (x) 的正整数都必须在区间 ([l,r]) 里出现过。那么我们肯定希望在满足上一条的同时,让区间 ([l,r]) 尽量长(这样更有可能包含到所有值)。所以如果存在合法的 ([l,r]),那也一定存在某个完整的线段是合法的。于是我们只需要对这 (k+1) 个线段进行判断即可。

问题转化为,对这 (k+1) 个线段,分别判断“所有小于 (x) 的正整数是否都在里面出现过”。也就是对它们求 ( ext{mex})

注意到,对于所有 (x),线段的总数(也就是 (sum(k+1)))只有 (O(n)) 个。


求区间 ( ext{mex}) 有一个经典的莫队做法。具体来说,我们先将这 (O(n)) 个要查询的区间离线下来。然后做莫队。需要支持每次插入 / 删除一个数,求当前集合的 ( ext{mex})

第一反应是在值域上建一棵线段树。但是这样插入 / 删除操作就是 (O(log n)) 的了,总时间复杂度 (O(nsqrt{n}log n))。事实上,发现我们需要的修改次数很多((O(nsqrt{n})) 次),但查询次数较少((O(n)) 次)。而线段树恰恰是修改较慢((O(log n))),查询较快(因为是全局查询,直接回答根节点信息,是 (O(1)) 的)。所以我们在这里用一个“根号平衡”,即用分块,牺牲查询的复杂度,实现较快的修改。具体来说,我们对值域分块,维护每个块内有几个出现次数非零的值。修改是单点修改,可以 (O(1)) 完成。查询时,从小到大暴力枚举每个块,遇到第一个没有满的块,就在这个块里暴力找到答案。这样查询是 (O(sqrt{n})) 的。

总时间复杂度 (O(nsqrt{n}))

参考代码:

// problem: CF1436E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5;
const int MAXM = MAXN * 2; // 考虑妙妙的常数
const int BLOCK_SIZE = 300, BLOCK_NUM = MAXN / BLOCK_SIZE + 10;

int n, a[MAXN + 5];
int st[BLOCK_NUM + 5], ed[BLOCK_NUM + 5], bel[MAXN + 5], cnt_block;
vector<int> vec[MAXN + 5];
vector<int> qid[MAXN + 5];
int ans[MAXM + 5], cntq;
struct Query_t {
	int l, r, id;
	Query_t(int _l, int _r, int _id) { l = _l; r = _r; id = _id; }
	Query_t() {}
}q[MAXM + 5];
bool cmp(Query_t lhs, Query_t rhs) {
	return (bel[lhs.l] == bel[rhs.l]) ? (lhs.r < rhs.r) : (lhs.l < rhs.l);
}

int buc[MAXN + 5], nemp[BLOCK_NUM + 5]; // nemp: not empty 块里非空的桶有几个
void modify(int p, int v) {
	if (buc[p]) nemp[bel[p]]--;
	buc[p] += v;
	if (buc[p]) nemp[bel[p]]++;
}
int get_mex() {
	for (int i = 1; i <= cnt_block; ++i) {
		if (nemp[i] == ed[i] - st[i] + 1)
			continue; // 全满了
		for (int j = st[i]; j <= ed[i]; ++j) {
			if (!buc[j])
				return j;
		}
		assert(0);
	}
	return n + 1;
}

int curl, curr;
void movel(int t) {
	if (t == 1) {
		// l 右移, 删数
		modify(a[curl], -1);
		++curl;
	} else {
		// l 左移, 加数
		--curl;
		modify(a[curl], 1);
	}
}
void mover(int t) {
	if (t == 1) {
		// r 右移, 加数
		++curr;
		modify(a[curr], 1);
	} else {
		// r 左移, 删数
		modify(a[curr], -1);
		curr--;
	}
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		vec[a[i]].pb(i);
	}
	for (int i = 1; i <= n; i += BLOCK_SIZE) {
		int j = min(n, i + BLOCK_SIZE - 1);
		++cnt_block;
		st[cnt_block] = i;
		ed[cnt_block] = j;
		for (int k = i; k <= j; ++k) {
			bel[k] = cnt_block;
		}
	}
	for (int i = 1; i <= n + 1; ++i) {
		if (!SZ(vec[i])) {
			++cntq;
			q[cntq] = Query_t(1, n, cntq);
			qid[i].pb(cntq);
		} else {
			if (vec[i][0] != 1) {
				++cntq;
				q[cntq] = Query_t(1, vec[i][0] - 1, cntq);
				qid[i].pb(cntq);
			}
			for (int j = 1; j < SZ(vec[i]); ++j) {
				if (vec[i][j] != vec[i][j - 1] + 1) {
					++cntq;
					q[cntq] = Query_t(vec[i][j - 1] + 1, vec[i][j] - 1, cntq);
					qid[i].pb(cntq);
				}
			}
			if (vec[i].back() != n) {
				++cntq;
				q[cntq] = Query_t(vec[i].back() + 1, n, cntq);
				qid[i].pb(cntq);
			}
		}
	}
	sort(q + 1, q + cntq + 1, cmp);
	curl = 1, curr = 0;
	for (int i = 1; i <= cntq; ++i) {
		while (curl > q[i].l) movel(-1);
		while (curr < q[i].r) mover(1);
		while (curl < q[i].l) movel(1);
		while (curr > q[i].r) mover(-1);
		ans[q[i].id] = get_mex();
	}
	for (int i = 1; i <= n + 2; ++i) {
		bool fail = 0;
		for (int j = 0; j < SZ(qid[i]); ++j) {
			if (ans[qid[i][j]] == i) {
				fail = 1;
				break;
			}
		}
		if (!fail) {
			cout << i << endl;
			return 0;
		}
	}
	assert(0);
}

CF1436F Sum Over Subsets

题目链接

题目要求 (A) 集合里数的 (gcd = 1)

考虑求出 (g_i),表示 (gcd = i) 的答案。最后输出 (g_1) 即可。

(gcd) 恰好等于 (i) 不好求。我们可以先求一个 (f_i),表示 (gcd)(i) 的倍数的答案,即 (f_i = sum_{i|j}g_j)。然后再容斥回去,或者套用莫比乌斯反演的式子:(g_i = sum_{i|j}f_jcdot mu(frac{j}{i})) 就能求出答案了。

依次枚举每个 (i),考虑求 (f_i)。记所有 (i) 的倍数组成的可重集为 (S)。则 (f_i = sum_{Bsubset Asubseteq S,|B|=|A|-1}sum_{xin A}x imes (sum_{yin B}y))。可以写成:(sum_{A,B}sum_{xin A,yin B}xcdot y)。那么我们考虑可重集 (S) 里任意一对 (x,y) 对答案的贡献。分两种情况。以下记在 (A) 中但不在 (B) 中的数为被踢出的数:

  1. (x,y) 是同一个数。对答案的贡献是 (x^2 imes (|S|-1) imes2^{|S|-2})。这个式子挺妙的。如果按照一般的想法,考虑 (A) 集合的大小,再从中选出一个数踢出,这就需要枚举集合大小,时间复杂度很高。而现在我们先选出要踢出的数(|S|-1) 种选法),再让剩下的数随意组成集合((2^{|S|-2}) 种方案),就不用枚举集合大小了!
  2. (x,y) 不是同一个数。对答案的贡献是 (x imes y imes((|S|-2) imes2^{|S|-3} + 2^{|S|-2}))。前半部分和上一种情况类似。后面的 (2^{|S|-2}) 是被踢出的数恰好是 (x)的情况。

( ext{freq}(x)) 表示数值 (x) 的出现次数(请注意,这里与输入格式中的定义略有不同)。由于 ( ext{freq}) 高达 (10^9)(S) 集合的大小是巨大的。我们不可能真的枚举所有数。所以我们只能枚举数值。此时需要进一步讨论:

  1. (x,y) 是同一个数(也就是上述的情况 1)。对答案的贡献是 (x^2 imes(|S|-1) imes2^{|S|-2})
  2. (x,y) 是数值相等的两个数。对答案的贡献是 (x^2 imes((|S| - 2) imes2^{|S|-3}+2^{|S|-2}) imes ext{freq}(x) imes( ext{freq}(x)-1))
  3. (x,y) 是数值不相等的两个数。对答案的贡献是 (x imes y imes((|S| - 2) imes2^{|S|-3}+2^{|S|-2}) imes ext{freq}(x) imes ext{freq}(y))。但是这里我们不能真的枚举两个数值。可以先预处理出集合 (S) 里所有数值的 (x imes ext{freq}(x)) 之和,记为 ( ext{sum})。这样只要枚举 (y),对 (f_i) 的贡献就是 (y imes ext{freq}(y) imes( ext{sum}-y imes ext{freq}(y)) imes((|S| - 2) imes2^{|S|-3}+2^{|S|-2}))

于是我们对于每个 (i),只需要枚举所有数值。即 (i,2i,3i,dots)。时间复杂度是调和级数,即 (O(nlog n))

参考代码:

// problem: CF1436F
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5;
const int MOD = 998244353;
inline int mod1(int x) { return x < MOD ? x : x - MOD; }
inline int mod2(int x) { return x < 0 ? x + MOD : x; }
inline void add(int &x, int y) { x = mod1(x + y); }
inline void sub(int &x, int y) { x = mod2(x - y); }
inline int pow_mod(int x, int i) {
	int y = 1;
	while (i) {
		if (i & 1) y = (ll)y * x % MOD;
		x = (ll)x * x % MOD;
		i >>= 1;
	}
	return y;
}

int p[MAXN + 5], cnt_p, mu[MAXN + 5];
bool v[MAXN + 5];
void sieve(int lim = MAXN) {
	mu[1] = 1;
	for (int i = 2; i <= lim; ++i) {
		if (!v[i]) {
			p[++cnt_p] = i;
			mu[i] = -1;
		}
		for (int j = 1; j <= cnt_p && p[j] * i <= lim; ++j) {
			v[i * p[j]] = 1;
			if (i % p[j] == 0) {
				break;
			}
			mu[i * p[j]] = -mu[i];
		}
	}
}
int n, m, freq[MAXN + 5];
int f[MAXN + 5];
int main() {
	sieve(n = MAXN);
	cin >> m;
	for (int i = 1; i <= m; ++i) {
		int a;
		cin >> a;
		cin >> freq[a];
	}
	for (int i = 1; i <= n; ++i) {
		ll tot = 0;
		for (int j = i; j <= n; j += i) {
			tot += freq[j];
		}
		if (tot < 2) continue;
		int w2, w3;
		w3 = pow_mod(2, (tot - 2) % (MOD - 1));
		w2 = (ll)(tot - 1) % MOD * w3 % MOD;
		if (tot >= 3) w3 = ((ll)w3 + (ll)(tot - 2) % MOD * pow_mod(2, (tot - 3) % (MOD - 1))) % MOD;
		
		for (int j = i; j <= n; j += i) if (freq[j]) {
			f[i] = ((ll)f[i] + (ll)j * j % MOD * w2 % MOD * freq[j]) % MOD;
			if (freq[j] >= 2) {
				f[i] = ((ll)f[i] + (ll)j * j % MOD * w3 % MOD * freq[j] % MOD * (freq[j] - 1)) % MOD;
			}
		}
		/*
		for (int j = i; j <= n; j += i) if (freq[j]) {
			for (int k = i; k <= n; k += i) if (k != j && freq[k]) {
				f[i] = ((ll)f[i] + (ll)j * k % MOD * w3 % MOD * freq[j] % MOD * freq[k]) % MOD;
			}
		}
		*/
		int sum = 0;
		for (int j = i; j <= n; j += i) if (freq[j]) {
			sum = ((ll)sum + (ll)j * freq[j]) % MOD;
		}
		for (int j = i; j <= n; j += i) if (freq[j]) {
			int cur = mod2(sum - (ll)j * freq[j] % MOD);
			f[i] = ((ll)f[i] + (ll)j * freq[j] % MOD * cur % MOD * w3) % MOD;
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		if (mu[i] == 1)
			add(ans, f[i]);
		if (mu[i] == -1)
			sub(ans, f[i]);
	}
	cout << ans << endl;
	return 0;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/13894322.html