• 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;
    }
    
  • 相关阅读:
    应对需求变更的软件的设计——我的想法
    重温数据库访问——故事篇
    用接口实现事件的一种方法,只是玩玩。
    面向对象最重要的是“抽象”,三层最重要的也是“抽象”,没有抽象就不是真正的面向对象、三层。
    【视频】自然框架之分页控件的使用方法(一) PostBack方式的一般分页方式
    【自然框架 免费视频】资源角色的思路介绍(整理了一下以前帖子的目录,请刷新)
    自信。
    钢铁是怎样炼成的?千锤百炼
    Android 中文API (94) —— MediaController
    android中文api(80)——Gallery.LayoutParams
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13894322.html
Copyright © 2020-2023  润新知