(注:本来开了个坑,原标题为「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))。本题多测。
(nin[1,100],a_iin[0,100],Tin[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<<"
",void();
if(fst)cout<<fst+101<<"
";
else cout<<202<<"
";
}
int main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
B - Maximum Product
洛谷题目页面传送门 & CF 题目页面传送门
给定 (n) 个整数(可正可负可零),求其中 (5) 个数的乘积的最大值。本题多测。
(ninleft[5,10^5 ight],sum nleq 2 imes10^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<<"
";
}
signed main(){
int testnum=1;
cin>>testnum;
while(testnum--)mian();
return 0;
}
C - Link Cut Centroids
洛谷题目页面传送门 & CF 题目页面传送门
给定一棵大小为 (n) 的树。要求删掉一条边加上一条边使重心唯一。给出方案。本题多测。
(ninleft[3,10^5 ight],sum nleq 10^5)。
假如说本来就唯一的话不用说。否则:
显然两个重心相邻。钦定其中一个为根,那么另一个就是第 (2) 层。我也不知道怎么想出来的,就随便试吧,可以删掉第 (2) 层重心的任意一条通向儿子的边(由 (ngeq 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
%d %d
",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
%d %d
",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,qinleft[1,10^5 ight])。
二话不说先找结论啊。通过观察样例发现,一开始 (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+sumlimits_{i=1}^{n-1}[Delta_igeq 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)<<"
";
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
",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) 种操作:
- 查询 (A) 中有多少个 (a) 的倍数((ain [1,n]));
- 查询 (A) 中有多少个 (a) 的倍数((ain[2,n])),并将 (A) 中所有 (a) 的倍数删去,特殊地,如果 (x) 是 (a) 的倍数则不删;
- 得出答案 (a),操作之后立即结束。
你需要在不超过 (10^4) 次操作内猜出某个预先设定好的 (xin A)。
(ninleft[2,10^5 ight])。
思路在于,通过操作 (1) 和操作 (2) 的配合,实现查询 (x) 是否是 (a) 的倍数。
不难想到尝试将 (x) 分解质因数,根据弱智的唯一分解定理可以确定 (x)。
最 naive 的想法是每个质数的幂都试一遍。打个表发现 (pi(n)) 大概是 (9500+),然后质数的幂大概是 (9700) 左右个。你可能会说,噫,好,这场 div. 2 我阿克了!哦上帝,瞧瞧这天真的声音。注意到操作 (2) 是先返回结果再删除的,也就是说它的返回结果是个幌子,你想知道 ([amid x]) 必须要先 (2) 再 (1),(2) 步走。
想到寿司晚宴那题的一个结论:将质数分成小质数和大质数,则每个数最多含有一个大质因子。
那么先将小质因子随便毛搞搞,我们设 ( au(m)) 表示 (m) 以内质数的幂的个数,那么是 (2 au(sqrt n)) 步的。实际上可以进一步优化,对于每个质数,先将它给 (2) 了,然后每个幂就可以直接查了。这样是 (pi(sqrt n)+ au(sqrt n)) 的。别看这一步优化微不足道,其实她能决定你是否 AC。
现在把质因数分解式里的小质因子部分已经分解出来了,并且 (A) 里显然只剩下 (1) 和 (x) 和所有大质数。接下来的任务就是要找出 (x) 是否有大质因子,如果有的话是谁。
需要分出两种情况:
- 小质因子部分 (>1)。那么显然 (x) 是不属于那个大质数集的。那还怕个鬼啊,直接检查所有的大质数乘以小质因子部分是否剩一个数,剩的话大质因子就是他了乘上去,否则没有;
- 小质因子部分 (=1)。此时就需要害怕了,因为你「检查所有的大质数乘以小质因子部分是否剩一个数」的话,那你任何一次检查结果都是「是」,就无语了。而此时问题变得更加简单,剩下来的集合就是 (1) 和所有大质数,而你可以确定 (x) 就在里面。考虑对大质数序列分块。每块整体删一下,然后如果集合大小减少数量不对劲就说明 (x) 一定在这块里面,集中精力搞。由于块大小不大,可以直接逐个用操作 (1) 排查。总操作次数大约为 (pi(sqrt n)+ au(sqrt n)+(pi(n)-pi(sqrt n))+2sqrt{pi(n)-pi(sqrt n)}= au(sqrt n)+pi(n)+2sqrt{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
",x),fflush(stdout);
cin>>x;return x;
}
int B(int x){
printf("B %d
",x);fflush(stdout);
cin>>x;return x;
}
void C(int x){printf("C %d
",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;
}