zoukankan      html  css  js  c++  java
  • 【题解】 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}\) 了。
  • 相关阅读:
    Visual Studio 2015 密钥
    Vue-next源码新鲜出炉一
    vue2.0基础整理
    Nest
    Nest
    Nest
    Nest
    Nest快速上手
    element-plus源码分析第一节
    获取视频第一帧,作为封面图
  • 原文地址:https://www.cnblogs.com/imakf/p/13660584.html
Copyright © 2011-2022 走看看