【题解】 Codeforces Round #670 (Div. 2)

人生巅峰,这是我第一次 \(\textrm{AK div2}\)

一场 \(\textrm{div2}\) 让我信心大增,感谢中国同胞!!!

Link \(\textrm{to Codeforces}\)

Contest

A. Subset Mex

Legend

给定长度为 \(n\ (1 \le n \le 100)\) 的数组 \(a\ (0 \le a_i \le 100)\),把它们分成两个集合,使得 \(\operatorname{mex}(S_1)+\operatorname{mex}(S_2)\) 尽量大,可以为空。

数据组数 \(1 \le t \le 100\)

Editorial

显然贪心构造即可。

Code

可以但没必要。

B. Maximum Product

Legend

给定长度为 \(n\ (5 \le n,\sum n \le 10^5)\) 的数组 \(a\ ( |a_i| \le 3 \times 10^3)\)。从中选 \(5\) 个数字,使得乘积尽可能大。

数据组数 \(1 \le t \le 2\cdot10^4\)

Editorial

显然排完序只能选两端的。

Code

可以但没必要。

Legend

给定一棵树,共 \(n\ (3 \le n,\sum n \le 10^5)\) 个节点,请删除一条边再加上一条边,使得图依然联通且树的重心有且仅有一个。

数据组数 \(1 \le t \le 10^4\)

Editorial

找到重心。

  • 如果只有一个,随便断一条边再连上。
  • 如果有两个,则它们肯定不是叶子,那么把其中的一个重心任选一个子树接到另一个重心去即可。

Code

int c1 ,c2 ,sz[MX] ,mx[MX] ,n;
void dfs(int x ,int f){
	mx[x] = 0;
	sz[x] = 1;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		dfs(d ,x);
		sz[x] += sz[d];
		mx[x] = max(mx[x] ,sz[d]);
	}
	mx[x] = max(mx[x] ,n - sz[x]);
	if(mx[x] == mx[c1]) c2 = x;
	if(mx[x] < mx[c1]){
		c1 = x;
		c2 = 0;
	}
}
 
void solve(){
	mx[0] = 114514;
	cin >> n;
	for(int i = 1 ; i <= n ; ++i) head[i] = 0;
	tot = c2 = c1 = 0;
	int u ,v;
	for(int i = 1 ; i < n ; ++i){
		cin >> u >> v;
		addedge(u ,v);
	}
	dfs(1 ,0);
	if(!c2){
		cout << u << " " << v << endl;
		cout << u << " " << v << endl;
	}
	else{
		
		for(int i = head[c1] ,d ; i ; i = h[i].next){
			if((d = h[i].node) != c2){
				cout << c1 << " " << d << endl;
				cout << d << " " << c2 << endl;
				break;
			}
		}
	}
}

D. Three Sequences

Legend

给定长度为 \(n\ (1 \le n \le 10^5)\) 的数组 \(a\ (|a_i| \le 10^9)\)

同时有另外两个长度与 \(a\) 相同的数组 \(b,c\) 满足:

  • \(b_i+c_i=a_i\)
  • \(b\) 单调不降。
  • \(c\) 单调不升。

你需要最小化 \(\max(b_i,c_i)\),输出这个结果。

以及还有 \(q\ (1 \le q \le 10^5)\) 次修改,将 \(a\) 区间 \([l,r]\) 加上 \(x\ (|x| \le 10^9)\)

Editorial

好题!有那么点意思。

因为 \(b\) 单调不降,我们考虑如下式子:

\(b_2=b_1+ \Delta_1\ (\Delta \ge 0)\)\(b_1+c_1=a_1\)\(b_2+c_2=a_2\)\(c_1 \ge c_2\)

整理可得:\(a_1+\Delta_1 \ge a_2\)

我们肯定是希望 \(\sum \Delta\) 尽可能小,所以 \(\Delta\) 取到等号最优,即 \(\Delta_i=\max(0,a_{i+1}-a_i)\)

我们要求的答案是 \(\max(b_n,c_1)\),那我们得先把这个式子表示出来。

根据定义我们可以得到 \(b_n = b_1 + \sum \Delta\)。我们肯定是希望 \(c_1,b_n\) 比较接近,这样才取到最优。

\(c_1=b_1+\sum \Delta\),整理得到 \(c_1 = \left\lceil \dfrac{a_1 + \sum \Delta}{2} \right\rceil\) 即为最终答案。

区间加只会改动差分数组的两个位置,改变了 \(\Delta\) 的值。

Code

LL a[MX] ,del[MX] ,cost ,n;

void upd(int pos ,int v){
	if(pos > n || pos == 1) return;
	LL before = max(del[pos] ,0LL);
	del[pos] += v;
	cost += max(del[pos] ,0LL) - before;
}

LL Ans(LL x){
	if(x >= 0) return (x + 1) / 2;
	return x / 2;
}

void solve(){
	cost = 0;
	cin >> n;
	for(int i = 1 ; i <= n ; ++i){
		cin >> a[i];
		del[i] = a[i] - a[i - 1];
		if(i != 1 && del[i] >= 0) cost += del[i];
	}
	cout << Ans(a[1] + cost) << endl;
	int q; cin >> q;
	for(int i = 1 ; i <= q ; ++i){
		int l ,r ,d; cin >> l >> r >> d;
		if(l == 1) a[1] += d;
		else{
			upd(l ,d);
		}
		if(r != n){
			upd(r + 1 ,-d);
		}
		cout << Ans(a[1] + cost) << endl;
	}
}

E. Deleting Numbers

Legend

本题为交互题。

你有一个集合 \(\{1,2,\cdots,n\}\ (1 \le n \le 10^5)\),现在有一个数 \(x\ (1 \le x \le n)\),你要猜它。你可以对集合进行如下操作:

  • 询问集合中有多少个 \(a\ (1 \le a \le n)\) 的倍数。
  • 询问集合中有多少个 \(a\ (1 \le a \le n)\) 的倍数,并把 \(a\) 的倍数删除。特别地,\(x\) 永远不会被删除。
  • 告诉交互器答案是 \(a\)。这个操作只能执行一次。

你最多可以操作 \(10^4\) 次。

Editorial

a useful web

这种题目嘛,所有人都会想到质数。于是我马上进了 \(\textrm{number empire}\) 网站(Link),搜索了 \(10^5\) 以内的质数个数,发现很巧,正好有 \(9592\) 个,与题目限制的 \(10^4\) 十分接近。发现到答案有可能是个大质数,因为每一个质数都至少要做一次操作 \(2\),所以询问次数的下界就是 \(9592\)我们还有 \(408\) 次可以做其他的事情。

a well-known conclusion

用到一个十分基础的结论:\(> \sqrt{n}\) 的质因子最多只有一个。

for prime \(\le \sqrt{n}\)

我马上发现 \(\sqrt{n}=316.227766\cdots\),发现 \(\le \sqrt{n}\) 的质数只有 \(65\) 个。我们不妨对这 \(65\) 个质数进行暴力操作,每次先删了 \(p\) 的倍数,再询问 $p1,p2,p^3\cdots $ 是否还有。发现这最多只需要 \(65+15\) 次就能搞定。(\(15\) 是因为 \(2^{17}>10^5\))。

for prime \(> \sqrt{n}\)

接下来对于 \(> \sqrt{n}\) 的质数还有 \(9528\) 个,但是这当中最多只有一个因子。我分了以下两种情况讨论:

  • 这个数存在 \(\le \sqrt{n}\) 的因子。

那么可以对每一个 \(> \sqrt{n}\) 的质数使用操作 \(1\),看元素数量对不对,没对上就说明肯定这个 \(>\sqrt{n}\) 的因子就是当前询问的。

  • 这个数不存在 \(< \sqrt{n}\) 的因子。

考虑每 \(\sqrt{9528}=97.611474\cdots\) 个质数分成一块,每删除完一个块中所有数就检查一下 \(1\) 的倍数有多少个(即集合元素数量),如果没对上就说明这个因子一定在这个块里面。暴力检查块内元素即可。最多消耗 \(98+98=196\) 次。

所以最坏情况消耗次数是 \(9592+65+15+98+98=9868\) 次,可以通过。

复杂度我不太会算,如果是枚举 \(1 \to n\),那复杂度是 \(O(n \log n)\) 但只枚举了质数,远远达不到。

**upd: ** 复杂度与埃氏筛是一样的,即 \(O(n \log \log n)\)

Code

交互题调试还要自己写交互器……

#include <bits/stdc++.h>

using namespace std;

#define LL long long

const int MX = 1e5 + 233;

int pri[MX] ,Npri[MX] ,n ,tot;
int vis[MX];
int shouldrem;

void sieve(){
	Npri[0] = Npri[1] = true;
	for(int i = 2 ; i <= n ; ++i){
		if(!Npri[i]) pri[++tot] = i ;
		for(int j = 1 ; j <= tot && pri[j] * i <= n ; ++j){
			int aim = pri[j] * i;
			Npri[aim] = 1;
			if(i % pri[j] == 0) break;
		}
	}
}

/*
int dele[MX] ,Ans;
int del(int x ,int v = 1){
	int ret = 0;
	for(int i = x ; i <= n ; i += x){
		ret += !dele[i];
		if(v == 0) continue;
		if(i != Ans) dele[i] = 1;
	}return ret;
}
*/
int AskB(int x){
	cout << "B " << x << endl;
	// return del(x);
	cin >> x; return x;
}

int AskA(int x){
	cout << "A " << x << endl;
	// return del(x ,0);
	cin >> x; return x;
}


int gar;
int main(){
	cin >> n;
	// Ans = 1949;
	shouldrem = n;
	sieve();
	
	int Ans = 1;
	int i = 1;
	for( ; i <= tot && pri[i] <= min(317 ,n) ; ++i){
		int p = pri[i];
		gar = AskB(p);
		for(int j = p ; j <= n ; j += p)
			shouldrem -= !vis[j] ,vis[j] = 1;
		
		int ok = AskA(p);
		if(ok) Ans *= pri[i];
		for(p *= pri[i] ; ok && p <= n ; p *= pri[i]){
			ok = AskA(p);
			if(ok) Ans *= pri[i];
		}
	}
	
	if(Ans != 1){
		for( ; i <= tot ; ++i){
			gar = AskB(pri[i]);
			int tmp = 0;
			for(int j = pri[i] ; j <= n ; j += pri[i])
				shouldrem -= !vis[j] ,tmp += !vis[j] ,vis[j] = 1;
			if(gar != tmp){
				Ans *= pri[i];
				break;
			}
		}
	}
	else{
		int cnt = 0;
		for( ; i <= tot ; ++i){
			++cnt;
			AskB(pri[i]);
			for(int j = pri[i] ; j <= n ; j += pri[i])
				shouldrem -= !vis[j] ,vis[j] = 1;
			if(cnt == 98 || i == tot){
				gar = AskA(1);
				if(gar != shouldrem){
					for(int j = 0 ; j < 98 ; ++j){
						gar = AskA(pri[i - j]);
						if(gar){
							Ans *= pri[i - j];
							goto out;
						}
					}
				}
				cnt = 0;
			}
		}
	}
	out:
	cout << "C " << Ans << endl;
	return 0;
}

Summary

是一场很有趣的 \(\textrm{div 2}\),虽说是中国场但没有什么中国 \(\textrm{OI}\) 的气息(指数据结构、多项式等)。考察的数学知识点较多,感觉 \(\textrm{PJ}\) 选手也能做起来友好的样子。

我自己要反思一点是:\(\rm E\) 其实并不算太难,却卡了我很久,有两点原因:

  • 对自己没有信心,高估 \(\textrm{2E}\) 难度,导致我想了半个小时没有想出来就开始慌张。以至于算分块复杂度的时候把质数数量看成了 \(10^5\),均值不等式一分析,询问次数不够……就更加慌张,还好最后发现了问题。
  • 对于交互题的不熟练,我测试样例就用了很久时间。交互题的询问什么的应该封装到一个函数里,这样子可以方便自己写交互库自己测试。我因为没有调试导致损失了 \(150\) 分,不然的话就可以到 \(\textrm{rank5}\) 了。
posted @ 2020-09-13 10:49  Imakf  阅读(638)  评论(3编辑  收藏  举报