CodeForces 1406 - Codeforces Round #670 (Div. 2)

(注:本来开了个坑,原标题为「do you know wtf it is???」,正文为「懵逼了吧」,所以被 hsc 骂了……)

这场比较简单,所以本文的重心放在一波三折的比赛(赛后?)经历上(

本来想着用 tzcWh... 这个号控制住不要上橙,大概控制在 2070~2099 左右,然后下一场 div. 2 就能超过大号这样。

比赛结束前 5min E 交上去 WA 了,于是弃了,看了眼 predictor,能直接上 2150,这不行啊。于是开始故意 hack 失败,网速比较慢所以 5min 只 hack 了 5 次。

减了 250pts 之后比赛结束,又看了一眼 predictor,+130?掐指一算,涨到 2102?wtf???要上橙就给我上高一点,要么就不要上橙,ntm 给我搭个橙名线是几个意思?很后悔没有早点开始 hack,多 hack 一次就 CM 稳了。然后就跟 wjz 疯狂祖安。

然后突然发现我的 A 好像 FST 了,上面出现了个红色的 -1?那 tgxl,rating 低一点没关系,至少不用上橙了!然后 wjz 说「这是 feature,你刷新一下」(老 system test 观众了),然后又没 FST,得分了。还我 FST!!!于是继续祖安,祖安累了去洗了个澡。

洗完澡回来之后发现 wjz 跟我说「不会 master」,又是啥?system test 已经结束了,打开 predictor 一看 +124,2096?tgxl,这个结果我满意。于是开开心心睡觉去了。

早上起来忐忑地打开电脑,发现居然 +127,2099?这无疑是我最满意的结果,请叫我控分带师(

也许这就是命运吧(


下面是题解(正文部分)

CF 比赛页面传送门

A - Subset Mex

洛谷题目页面传送门 & CF 题目页面传送门

给定一个集合 \(a,|a|=n\),将它分成两个集合 \(A,B\),要求最大化 \(\operatorname{mex}(A)+\operatorname{mex}(B)\)。本题多测。

\(n\in[1,100],a_i\in[0,100],T\in[1,100]\)

随便贪心就好了,两个集合的 \(\operatorname{mex}\) 齐头并进,某个进不了了就停下来,两个都停下来的时候就是答案最大化的情况。代码也随便写了吧。

#include<bits/stdc++.h>
using namespace std;
const int N=100;
int n;
int a[N+1];
int cnt[N+1];
void mian(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	sort(a+1,a+n+1);
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=n;i++)cnt[a[i]]++;
	int fst=0;
	for(int i=0;i<=100;i++)if(cnt[i]<2){fst=i;break;}
	for(int i=fst;i<=100;i++)if(cnt[i]<1)return cout<<fst+i<<"\n",void();
	if(fst)cout<<fst+101<<"\n";
	else cout<<202<<"\n";
}
int main(){
	int testnum=1;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

B - Maximum Product

洛谷题目页面传送门 & CF 题目页面传送门

给定 \(n\) 个整数(可正可负可零),求其中 \(5\) 个数的乘积的最大值。本题多测。

\(n\in\left[5,10^5\right],\sum n\leq 2\times10^5\)

这个就分个两种情况吧,有 \(0\) 和无 \(0\)

\(0\) 的话就是 \(0\) 了。

\(0\) 的话就枚举正数个数,然后排个序贪个心就切了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
int n;
void mian(){
	cin>>n;
	vector<int> po,ne;
	int ans=-inf;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		if(x>0)po.pb(x);
		if(x==0)ans=0;
		if(x<0)ne.pb(x);
	}
	sort(po.begin(),po.end(),greater<int>());
	sort(ne.begin(),ne.end());
	for(int i=0;i<=5;i++){
		if(i<=po.size()&&5-i<=ne.size()){
			int res=1;
			if(5-i&1){
				for(int j=(int)(po.size())-1;j>=(int)(po.size())-i;j--)res*=po[j];
				for(int k=(int)(ne.size())-1;k>=(int)(ne.size())-(5-i);k--)res*=ne[k];
			}
			else{
				for(int j=0;j<i;j++)res*=po[j];
				for(int k=0;k<5-i;k++)res*=ne[k];
			}
			ans=max(ans,res);
		}
	}
	cout<<ans<<"\n";
}
signed main(){
	int testnum=1;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

洛谷题目页面传送门 & CF 题目页面传送门

给定一棵大小为 \(n\) 的树。要求删掉一条边加上一条边使重心唯一。给出方案。本题多测。

\(n\in\left[3,10^5\right],\sum n\leq 10^5\)

假如说本来就唯一的话不用说。否则:

显然两个重心相邻。钦定其中一个为根,那么另一个就是第 \(2\) 层。我也不知道怎么想出来的,就随便试吧,可以删掉第 \(2\) 层重心的任意一条通向儿子的边(由 \(n\geq 3\) 易证一定有儿子),然后把那个儿子连向根即可。

证明的话,显然删加过之后根依然是重心,因为子树大小最大值变小了嘛。而第 \(2\) 层那个的相对应的显然变大了,那么它们的最大子树大小就不等了,就不可能同时为重心。然后如何证其他点不为重心呢?因为要想是重心就必须与根相邻,而这三个点发生关系跟其他儿子有个屁的关系,那最大子树大小肯定就不变啊,就无法翻身。得证。

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=100000;
int n;
vector<int> nei[N+1];
int sz[N+1];
void dfs(int x=1,int fa=0){
	sz[x]=1;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa)continue;
		dfs(y,x);
		sz[x]+=sz[y];
	}
}
void mian(){
	cin>>n;
	for(int i=1;i<=n;i++)nei[i].clear();
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		nei[x].pb(y);nei[y].pb(x);
	}
	dfs();
	vector<int> cen;
	for(int i=1;i<=n;i++){
		bool flg=true;
		int sum=1;
		for(int j=0;j<nei[i].size();j++){
			int x=nei[i][j];
			if(sz[x]>sz[i])continue;
			flg&=sz[x]<=n/2;
			sum+=sz[x];
		}
		flg&=n-sum<=n/2;
		if(flg)cen.pb(i);
	}
	assert(cen.size()<=2);
	if(cen.size()==1)printf("%d %d\n%d %d\n",1,nei[1][0],1,nei[1][0]);
	else{
		int son=nei[cen[0]][0]==cen[1]?nei[cen[0]][1]:nei[cen[0]][0];
		printf("%d %d\n%d %d\n",cen[0],son,cen[1],son);
	}
}
int main(){
	int testnum=1;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

D - Three Sequences

洛谷题目页面传送门 & CF 题目页面传送门

给定一个长度为 \(n\) 的数列 \(a\)。你需要构造出长度为 \(n\) 的数列 \(b,c\),满足 \(a_i=b_i+c_i\),且 \(b\) 不降,\(c\) 不升。最小化 \(\max(b_i,c_i)\)。然后还有 \(q\) 次区间增加,每次输出最小化的结果。

\(n,q\in\left[1,10^5\right]\)

二话不说先找结论啊。通过观察样例发现,一开始 \(b_1,c_1\) 随便取只要满足 \(a_1=b_1+c_1\) 即可,然后以后的话,若 \(a\) 的增量 \(\geq 0\) 就在 \(b\) 上加,否则就在 \(c\) 上减。证明的话随便想想很简单,显然 \(\max(b_i,c_i)=\max(b_n,c_1)\),那么在 \(b_1,c_1\) 固定的时候,\(b\) 的总增量显然越小越好,于是就只有在必要的时候才在 \(b\) 上加咯。

然后现在解决 \(b_1,c_1\) 不固定的事情。我们想要令 \(\max(b_n,c_1)\) 这个柿子最小,那么首先需要将 \(b_n\)\(b_1\) 表示一下。令增量 \(\Delta_i=a_{i+1}-a_i\),则 \(b_n=b_1+\sum\limits_{i=1}^{n-1}[\Delta_i\geq 0]\Delta_i\)。令那个 \(\sum\)\(\Sigma\),那么柿子为 \(\max(b_1+\Sigma,c_1)\)。又 \(a_1=b_1+c_1\),则柿子又可以写为 \(\max(b_1+\Sigma,a_1-b_1)\)。那么注意到 \(\max\) 两个参数是和为常量的,那么最理想的情况是令它们相等,则 \(\max\) 最小。解个方程可以得到 \(b_1=\dfrac{a_1-\Sigma}2\)。但是不允许出现小数,所以还要取个整然后左右两边 round 一下。

然后还有区间加呢。注意到区间加只会令两个增量变化,于是随便 \(\mathrm O(1)\) 维护即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=100000;
int n;
int a[N+1];
int qu;
int d[N+1];
int calc(int now,int x){
	int res=inf;
	for(int i=now-3;i<=now+3;i++)res=min(res,max(i+x,a[1]-i));
	return res;
}
void mian(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",a+i);
	for(int i=1;i<n;i++)d[i]=a[i+1]-a[i];
	int x=0;
	for(int i=1;i<n;i++)if(d[i]>0)x+=d[i];
	cout<<calc(a[1]-x>>1,x)<<"\n";
	cin>>qu;
	while(qu--){
		int l,r,v;
		scanf("%lld%lld%lld",&l,&r,&v);
		if(l==1)a[1]+=v;
		if(l-1&&d[l-1]>0)x-=d[l-1];
		if(r<n&&d[r]>0)x-=d[r];
		d[l-1]+=v,d[r]-=v;
		if(l-1&&d[l-1]>0)x+=d[l-1];
		if(r<n&&d[r]>0)x+=d[r];
		printf("%lld\n",calc(a[1]-x>>1,x));
	}
}
signed main(){
	int testnum=1;
//	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

E - Deleting Numbers

这是钓鱼王子出的好题哦~

洛谷题目页面传送门 & CF 题目页面传送门

本题为交互题。给定 \(n\),初始时有 \(A=[1,n]\cap \mathbb Z\)。你有 \(3\) 种操作:

  1. 查询 \(A\) 中有多少个 \(a\) 的倍数(\(a\in [1,n]\));
  2. 查询 \(A\) 中有多少个 \(a\) 的倍数(\(a\in[2,n]\)),并将 \(A\) 中所有 \(a\) 的倍数删去,特殊地,如果 \(x\)\(a\) 的倍数则不删;
  3. 得出答案 \(a\),操作之后立即结束。

你需要在不超过 \(10^4\) 次操作内猜出某个预先设定好的 \(x\in A\)

\(n\in\left[2,10^5\right]\)

思路在于,通过操作 \(1\) 和操作 \(2\) 的配合,实现查询 \(x\) 是否是 \(a\) 的倍数。

不难想到尝试将 \(x\) 分解质因数,根据弱智的唯一分解定理可以确定 \(x\)

最 naive 的想法是每个质数的幂都试一遍。打个表发现 \(\pi(n)\) 大概是 \(9500+\),然后质数的幂大概是 \(9700\) 左右个。你可能会说,噫,好,这场 div. 2 我阿克了!哦上帝,瞧瞧这天真的声音。注意到操作 \(2\) 是先返回结果再删除的,也就是说它的返回结果是个幌子,你想知道 \([a\mid x]\) 必须要先 \(2\)\(1\)\(2\) 步走。

想到寿司晚宴那题的一个结论:将质数分成小质数和大质数,则每个数最多含有一个大质因子。

那么先将小质因子随便毛搞搞,我们设 \(\tau(m)\) 表示 \(m\) 以内质数的幂的个数,那么是 \(2\tau(\sqrt n)\) 步的。实际上可以进一步优化,对于每个质数,先将它给 \(2\) 了,然后每个幂就可以直接查了。这样是 \(\pi(\sqrt n)+\tau(\sqrt n)\) 的。别看这一步优化微不足道,其实她能决定你是否 AC。

现在把质因数分解式里的小质因子部分已经分解出来了,并且 \(A\) 里显然只剩下 \(1\)\(x\) 和所有大质数。接下来的任务就是要找出 \(x\) 是否有大质因子,如果有的话是谁。

需要分出两种情况:

  1. 小质因子部分 \(>1\)。那么显然 \(x\) 是不属于那个大质数集的。那还怕个鬼啊,直接检查所有的大质数乘以小质因子部分是否剩一个数,剩的话大质因子就是他了乘上去,否则没有;
  2. 小质因子部分 \(=1\)。此时就需要害怕了,因为你「检查所有的大质数乘以小质因子部分是否剩一个数」的话,那你任何一次检查结果都是「是」,就无语了。而此时问题变得更加简单,剩下来的集合就是 \(1\) 和所有大质数,而你可以确定 \(x\) 就在里面。考虑对大质数序列分块。每块整体删一下,然后如果集合大小减少数量不对劲就说明 \(x\) 一定在这块里面,集中精力搞。由于块大小不大,可以直接逐个用操作 \(1\) 排查。总操作次数大约为 \(\pi(\sqrt n)+\tau(\sqrt n)+(\pi(n)-\pi(\sqrt n))+2\sqrt{\pi(n)-\pi(\sqrt n)}=\tau(\sqrt n)+\pi(n)+2\sqrt{\pi(n)-\pi(\sqrt n)}\),卡的死死的,出题人真是毒瘤。
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
bool ispr(int x){
	if(x<2)return false;
	for(int i=2;i*i<=x;i++)if(x%i==0)return false;
	return true;
}
int A(int x){
	printf("A %d\n",x),fflush(stdout);
	cin>>x;return x;
}
int B(int x){
	printf("B %d\n",x);fflush(stdout);
	cin>>x;return x;
}
void C(int x){printf("C %d\n",x);fflush(stdout);exit(0);}
void mian(){
	int n;
	cin>>n;
	if(n==1)C(1);
	vector<int> pr;
	for(int i=2;i<=n;i++)if(ispr(i))pr.pb(i);
	int x=1,lim=sqrt(n);
	for(int i=0;pr[i]<=lim;i++){
		B(pr[i]);
		int now=1;
		while(now*pr[i]<=n)now*=pr[i];
		while(now>1){
			if(A(now)==1){x*=now;break;}
			now/=pr[i];
		}
	}
	vector<int> v;
	for(int i=0;i<pr.size();i++)if(pr[i]>lim)v.pb(pr[i]);
	if(x==1){
		int now=A(1);
		for(int i=0;i<v.size();i+=100){
			for(int j=i;j<i+100&&j<v.size();j++)B(v[j]);
			int res=A(1);
			if(now-res==min(i+100,int(v.size()))-i)now=res;
			else{
				for(int j=i;j<i+100&&j<v.size();j++)if(A(v[j])==1){x=v[j];break;}
				break;
			}
		}
	}
	else{
		for(int i=0;i<v.size();i++)if(1ll*x*v[i]<=n&&A(x*v[i])==1){x*=v[i];break;}
	}
	C(x);
}
int main(){
	int testnum=1;
//	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
posted @ 2020-09-13 22:24  ycx060617  阅读(318)  评论(4编辑  收藏  举报