Atcoder 水题选做

2023-03-18,

  为什么是水题选做呢?因为我只会水题啊 (

  为什么是$Atcoder$呢?因为暑假学长来讲课的时候讲了三件事:不要用洛谷,不要用dev-c++,不要用单步调试。$bzoj$太难了,$Topcoder$整了好久没学会用,中国人要长期打$codeforces$那作息简直反人类,所以就选到$atcoder$啦.而且$AT$的英语感觉比较好懂,或许是因为都是亚洲人,思维比较同步?

  那么哪些题目会被放到这个地方呢?$ABC$的后两题,也就是$ARC$的前两题(如果同时举办的话).至于$ARC$的后两题以及$AGC$的题,以后可能会单开一篇来写吧.似乎每当$ABC$和$ARC$一起办的时候就稍微难一点,只办$ABC$的时候题就非常水.

  做了两套题后感觉有些$C$的思路还是比较妙的,最好评的是即使是$D$也不会有很高的代码难度。前段时间掉进了大数据结构&&实现麻烦的题目的大坑,现在发现自己的思维好差啊...以后要多做一点思维题.

  

  107 C:https://arc101.contest.atcoder.jp/tasks/arc101_a

  题意概述:数轴上放有$n$根蜡烛,初始时站在$0$坐标,移动的速度是一个定值,求最少用多少时间可以点亮$k$根蜡烛.

  尺取法+分类讨论,如果一段区间横跨$0$坐标,那就先走离$0$近的部分,再折回来点亮另一边的,如果与$0$没有公共部分,直接一次走过去即可.

  

 # include <cstdio>
# include <iostream>
# include <cstring>
# include <string>
# include <cmath>
# include <algorithm>
# define R register int using namespace std; const int maxn=;
int n,k,p1,p2,equ;
int cnt,a[maxn],ans; int ab (int x)
{
if(x<) return -x;
return x;
} int ask (int x,int y)
{
if(x<&&y<) return -min(x,y);
if(x>&&y>) return max(x,y);
return ab(x)+ab(y)+min(ab(x),ab(y));
} int main()
{
scanf("%d%d",&n,&k);
for (R i=;i<=n;++i)
{
scanf("%d",&a[i]);
if(a[i]<) equ=i;
}
p1=p2=equ;
if(p2<k) p2=p1=k;
cnt=;
while (cnt<k&&p1>) p1--,cnt++;
if(cnt==k) ans=ask(a[p1],a[p2]);
while (p2<n)
{
p1++;
p2++;
ans=min(ask(a[p1],a[p2]),ans);
}
printf("%d",ans);
return ;

107 C

  107 D:https://arc101.contest.atcoder.jp/tasks/arc101_b

  题意概述:给定一个长度为$n$的序列,对它的所有连续子区间拿出来求中位数(如果是偶数长度就取较大的那个),再将这些中位数排起来求中位数,问这个中位数是几.$n<=10^5$

  中位数真是一个神奇的东西...

  看到中位数就可以试一下二分答案加上离散化,这道题也不例外.首先可以算出来最终的序列一共有多少个数,所以有多少个数字应该比中位数小也就可以算出来了.二分答案之后怎么做呢?可以首先根据与$ans$的大小关系把序列离散化成$-1,1$,求一个前缀和,现在的问题是怎么求出有多少个连续序列的中位数小于$ans$,容易发现如果$s_i-s_{j-1}>0$,即$s_i>s_{j-1}$,那么$s_j-s_i$就是一个合法的区间了,使用树状数组统计即可.因为树状数组的下标不能为负数,所以统一加上一个$n+1$平移到正数.注意因为二分时可能分到不属于这个序列的数,所以二分时应当取$min$.时间复杂度$O(Nlog^2N)$

  

 # include <cstdio>
# include <cstring>
# include <iostream>
# define R register int
# define ll long long using namespace std; const int maxn=;
int n;
int a[maxn],m,l,r,mid,ans,c[maxn],t[maxn<<]; void ins (int x) { for (R i=x;i<=*n+;i+=(i&(-i))) t[i]++; }
int ask (int x) { int ans=; for (R i=x;i;i-=(i&(-i))) ans+=t[i]; return ans; } bool check (int ans)
{
ll cnt=;
memset(t,,sizeof(t));
for (R i=;i<=n;++i)
if(a[i]<=ans) c[i]=c[i-]+;
else c[i]=c[i-]-;
ins(n+);
for (R i=;i<=n;++i)
{
cnt+=ask(c[i]+n);
ins(c[i]+n+);
}
ll b=1LL*n*(n+)/;
b/=;
return cnt>=b+;
} int main()
{
scanf("%d",&n);
for (R i=;i<=n;++i)
scanf("%d",&a[i]),m=max(m,a[i]);
l=,r=m,ans=m;
while(l<=r)
{
mid=(l+r)>>;
if(check(mid))
ans=min(ans,mid),r=mid-;
else
l=mid+;
}
printf("%d",ans);
return ;
}

D

  109 C:https://abc109.contest.atcoder.jp/tasks/abc109_c

  题意概述:给出$n$个城市的坐标以及起始坐标,每次可以移动$d$的距离(或正或负),求最大的$d$使得可以从起点出发经过所有点。

  首先$d$必然是任意点到初始点的距离的因子,否则到不了,然后...答案就是所有点离初始点距离的最大公约数了。(这道$C$是挺水的) 

  

 # include <cstdio>
# include <iostream> using namespace std; int n,x,ans;
int a[]; int gcd (int a,int b)
{
return b?gcd(b,a%b):a;
} int main()
{
scanf("%d%d",&n,&x);
for (int i=;i<=n;++i)
{
scanf("%d",&a[i]);
a[i]-=x;
if(a[i]<) a[i]=-a[i];
}
ans=a[];
for (int i=;i<=n;++i)
ans=gcd(ans,a[i]);
printf("%d",ans);
return ;
}

109 C

  109 D:https://abc109.contest.atcoder.jp/tasks/abc109_d

  题意概述:给定一个$H*W$的矩阵,每个格子中有一些硬币,每次可以从一个格子向与他四联通的任意一个格子移动一个硬币(只能移动一次),使得最终的局面中有偶数个硬币的格子最多。

  我感觉这个题挺好的...因为只能移动一次,可以将每个格子的硬币数量$\%2$表示奇偶性,现在问题是怎样移动使得为$0$的格子最多.四联通是一个比较麻烦的东西,因为可能导致处理完的格子再变化.但是可以发现只需要考虑向下和向右移动的情况.偶+偶=偶,奇+奇=偶,偶+奇=奇.从偶数格子里往外移动一定是不合算的,不过两个奇数格子互相传递,中间经过一个偶数格子还是可以的.从一个奇数格子往偶数格子移动一定不会使解更劣:如果后来又将这个多余的硬币移到了奇数格子就可以使解加一,即使不可以,也不过是调换了这两个格子的奇偶性.这是这道题的解法就基本成型了:从上到下遍历棋盘,如果遇到奇数就往右边移动一个,如果已经是最后一列就考虑往下移动,这样构造出来的答案一定是最优的。

  

 # include <cstdio>
# include <iostream> using namespace std; int h,w,f=,j,x,lasx,lasy;
int g[][];
int H,a[],b[],c[],d[]; int main()
{
scanf("%d%d",&h,&w);
for (int i=;i<=h;++i)
for (int j=;j<=w;++j)
scanf("%d",&g[i][j]),g[i][j]%=;
for (int i=;i<=h;++i)
{
if(i%) j=; else j=w;
while (<=j&&j<=w)
{
if(x)
{
a[++H]=lasx,b[H]=lasy,c[H]=i,d[H]=j;
if(g[i][j]) x=;
}
else if(g[i][j]) x=;
lasx=i;
lasy=j;
j+=f;
}
f=-f;
}
printf("%d\n",H);
for (int i=;i<=H;++i)
printf("%d %d %d %d\n",a[i],b[i],c[i],d[i]);
return ;
}

109 D

  110 C:https://abc110.contest.atcoder.jp/tasks/abc110_c

  题意概述:给出两个字符串,要求构造一个字符的映射关系,使得$S$=$T$.

  这题的难度一半在于读题,题面里那个置换非常有迷惑性,题目说的是可以任意将两种字母进行整体交换,其实就是要找到一个映射关系.

  似乎就是$NOIP$潜伏者那道题?总之就是开两个桶记录两个集合之间的对应关系,如果出现矛盾就...有矛盾啊.

  

 # include <cstdio>
# include <iostream>
# include <queue>
# include <algorithm>
# include <cstring>
# include <string>
# define R register int
# define ll long long using namespace std; const int maxn=;
char s[maxn],t[maxn];
int a[],b[],len,ans=; int main()
{
scanf("%s",s+);
scanf("%s",t+);
len=strlen(s+);
for (R i=;i<=len;++i)
{
if(a[ s[i]-'a'+ ]==) a[ s[i]-'a'+ ]=t[i]-'a'+;
else if(a[ s[i]-'a'+ ]!=t[i]-'a'+) ans=;
if(b[ t[i]-'a'+ ]==) b[ t[i]-'a'+ ]=s[i]-'a'+;
else if(b[ t[i]-'a'+ ]!=s[i]-'a'+) ans=;
}
if(ans) printf("No");
else printf("Yes");
return ;
}

110 C

  111 D:https://abc110.contest.atcoder.jp/tasks/abc110_d

  题意概述:给出$n,m$,问满足下面式子的数列有多少个.$n<=10^5,m<=10^9$

  

  $10^9$基本上就钦定是根号级别的算法了,线性是不可能的,$log$还不如开到$long$ $long$,大胆猜测首先要分解$m$。

  题目就被转化成了,将$m$的质因数排成一排,可以任意合并,求个数小于等于$n$的方案数.虽然题目说的是严格等于$n$,然而不够时添$1$即可.

  然后就会发现这个转化毫无意义~,而且还提高了编写的难度.

  可以用另一种思路来做这个题,考虑设置$n$个盒子,每个盒子里存一个数,一开始都是$1$,每次往盒子里面放数就相当于把这个数乘进去.此时我们有了一个非常有利于解题的模型,对于每种质因数单独考虑,利用乘法原理统计答案.对于每种质因数应当怎么做呢?等价于将$n$个无差别物体放进$m$个有差别盒子中,允许有空盒子,设置$m$个虚点后插板法即可.

 # include <cstdio>
# include <iostream>
# include <queue>
# include <cstring>
# include <string>
# define mod
# define R register int
# define ll long long using namespace std; const int maxn=;
int n,m,fac=,h;
int p[maxn],c[maxn];
ll f[maxn],inv[maxn];
ll ans=; ll qui (ll a,ll b)
{
ll s=;
while (b)
{
if(b&1LL) s=s*a%mod;
a=a*a%mod;
b=b>>1LL;
}
return s%mod;
} ll C (int a,int b)
{
return f[b]*qui(f[a],mod-)%mod*qui(f[b-a],mod-)%mod;
} int main()
{
scanf("%d%d",&n,&m);
f[]=;
for (R i=;i<=n+;++i)
f[i]=1LL*i*f[i-]%mod;
for (R i=;1LL*i*i<=m;++i)
{
if(m%i==) p[++h]=i;
while (m%i==) c[h]++,m/=i;
}
if(m!=) p[++h]=m,c[h]=;
for (R i=;i<=h;++i)
ans=(ans*C(n-,n+c[i]-))%mod;
printf("%lld",ans%mod);
return ;
}

110 D

  111 C:https://abc111.contest.atcoder.jp/tasks/arc103_a

  2018.10.6:一个月没做发现只缺了两场,开心~

  题意概述:给定一个长度为$n$的序列(保证$n$是偶数),可以随意改动序列中的数字,要求改成ABABABAB的形式,($A!=B$),求最少需要改动的次数.

  自以为是道很水的题,然而$WA$了两次,要注意细节啊...其实思路挺简单的,对于奇数部分和偶数部分分开考虑,找到出现次数最多的那个数字,之后将不是这个数字的数字都变成这个就好了...吗?注意$A!=B$,这时我的做法就处理不了了.其实加上这个限制也没有麻烦多少,再记录两个次大值,如果出现冲突就找一个次大值换上,这道题就做完了.最近见了不少记录次大值的题目,过两天凑上几个一起写一下.

  

 # include <cstdio>
# define R register int using namespace std; const int maxn=;
int n,M,ans,cnt,a[maxn],v[maxn],b1,b2,mb1,mb2,mc1,mc2,c1,c2; inline int min (int a,int b) { return b>=a?a:b; }
inline int max (int a,int b) { return b>=a?b:a; } int main()
{
scanf("%d",&n);
for (R i=;i<=n;++i)
scanf("%d",&a[i]),M=max(M,a[i]);
for (R i=;i<=n;i+=)
v[ a[i] ]++;
for (R i=;i<=M;++i)
if(v[i]>=mb1)
b2=b1,mb2=mb1,mb1=v[i],b1=i;
else
if(v[i]>mb2) mb2=v[i],b2=i;
for (R i=;i<=n;i+=)
v[ a[i] ]--;
for (R i=;i<=n;i+=)
v[ a[i] ]++;
for (R i=;i<=M;++i)
if(v[i]>=mc1)
c2=c1,mc2=mc1,mc1=v[i],c1=i;
else
if(v[i]>mc2) mc2=v[i],c2=i;
if(b1!=c1)
ans=n-mb1-mc1;
else
ans=min(n-mb1-mc2,n-mb2-mc1);
printf("%d",ans);
return ;
}

111 C

  111 D:https://abc111.contest.atcoder.jp/tasks/arc103_b

  题意概述:构造题。给定平面上的$n$个点,要求构造一条包含多个部分的机械臂,每个部分之间有一个关节,起点在$(0,0)$位置,每个关节处可以选择四种方向,要求终点处正好在给定点上。需要构造的部分:部分的数量,每段的长度,对于每一个点应采取的方向序列。无解输出$-1$

  一看到这道题以为自己又可以开心的水一道水题了,结果是被水题给水了...后来发现这次是和$ARC$一起办的,所以难一些.首先我觉得每个部分如果都是$1$肯定是最好的,非常灵活,部分数量就是所有点中距离原点的曼哈顿距离最大的那个距离.如果有两个点的曼哈顿距离的奇偶性不同,那么这两个点就不可能被同时到达,为无解.这个做法听起来非常美好,$OLE$之后才发现题目上写了一行$m<=40$,也就是不能超过四十个部分...以后再也不用全屏翻译的功能了,把这句话都给翻译成乱码了.看了题解发现是一个二进制构造,其实这种拼凑数字的题好多都是二进制,但是这个题包装的比较复杂,就没看出来.判断无解那里我写的是正确的,的确要求奇偶性相同,构造方法如下:从$2^{31}$开始,把每个$2$的幂次方都放上,如果目标距离是偶数就再加一条长度为一的边.摆放的时候采取一种修修补补的策略,目前哪个方向离目标差的比较多就把下一条机械臂指向那个方向即可.至于证明,有一个博客写的非常好:https://blog.csdn.net/zxyoi_dreamer/article/details/82905515

  

 # include <cstdio>
# include <iostream>
# include <cstring>
# include <cmath>
# include <algorithm>
# include <string>
# define ll long long
# define R register int using namespace std; const int maxn=;
int n,m,f=-;
ll x[maxn],y[maxn];
ll d[],len; inline ll ab (ll x) { if(x<) return -x; return x; } inline void solve (ll x,ll y)
{
for (R i=;i<=m-((f&)^);++i)
{
if(ab(x)>ab(y))
{
if(x>) putchar('R'),x-=d[i];
else putchar('L'),x+=d[i];
}
else
{
if(y>) putchar('U'),y-=d[i];
else putchar('D'),y+=d[i];
}
}
if(f%==) putchar('L');
putchar('\n');
} int main()
{
scanf("%d",&n);
for (R i=;i<=n;++i)
{
scanf("%lld%lld",&x[i],&y[i]);
len=ab(x[i])+ab(y[i]);
if(f==-) f=len%;
if(f%!=len%)
{
printf("-1");
return ;
}
}
for (R i=;i>=;--i) d[++m]=(1LL<<i);
if(f%==) d[++m]=;
printf("%d\n",m);
for (R i=;i<=m;++i)
{
printf("%lld",d[i]);
if(i==m) printf("\n");
else printf(" ");
}
for (R i=;i<=n;++i)
solve(x[i]+((f&)^),y[i]);
return ;
}

111 D

  112 C:https://abc112.contest.atcoder.jp/tasks/abc112_c

  本来今天是要做生成树的题目的,但是重启电脑后发现用来列任务清单的软件没了...所以又来做$AT$了.

  这场$AT$好像挺水的,竟然有$576$个$AK$选手,$\%\%\%$

  题意概述:有一个金字塔,中心点位于$(C_x,C_y)$每个点的高度有这样一个计算公式:$max(H-|x-C_x|-|y-C_y|,0)$,给出$n$个点的坐标及其高度,试求出金字塔的中心点。

  看到这道题觉得简直太妙啦,是不是要用到绝对值化简的性质呢?是不是可以二分套二分呢?这时我看到了数据范围:保证中心点的横纵坐标都是整数,且位于区间$[0,100]$,给出点的数量不会超过$100$...突然想起金牌爷之前跟我说的"做思维题做多了,每见到一个题就有五六种奇思妙想,其实暴力就行"...

  注意...边界...有一个小细节:如果某一次的高度为$0$,其实也就是给金字塔的高度设定了一个上限,这里一定要记住保存一下这个边界,等到输出前再看一看高度是否超出了这个边界即可$AC$

  

 # include <cstdio>
# include <iostream>
# define R register int using namespace std; const int maxn=;
int n,w;
int x[maxn],y[maxn],h[maxn]; inline int ab (int a) { if(a<) return -a; return a; } inline bool check (int cx,int cy)
{
int hh,X=;
w=-;
for (R i=;i<=n;++i)
{
if(h[i]==)
{
if(w==-)
{
X=min(X,ab(x[i]-cx)+ab(y[i]-cy));
continue;
}
if(w-ab(x[i]-cx)-ab(y[i]-cy)>) return false;
continue;
}
hh=ab(x[i]-cx)+ab(y[i]-cy)+h[i];
if(w==-) w=hh;
if(hh!=w) return false;
}
if(w==-) return false;
if(w>X) return false;
return true;
} int main()
{
scanf("%d",&n);
for (R i=;i<=n;++i)
scanf("%d%d%d",&x[i],&y[i],&h[i]);
for (R i=;i<=;++i)
for (R j=;j<=;++j)
{
if(check(i,j))
{
printf("%d %d %d",i,j,w);
return ;
}
}
return ;
}

112 C

  112 D:https://abc112.contest.atcoder.jp/tasks/abc112_d

  题意概述:把一个数分成$n$份,使这$n$个数的最大公约数最大,输出.

  $AT$还真喜欢出构造题.然而我最不会的就是构造题了.

  这道题几乎可以说是我自己做出来的第一道构造啦,留念$2018-10-08$ $21:38:31$

  首先我们可以对这个式子进行一番变形:$k*a_1+k*a_2+……+k*a_n=M$,再提取一下公因数:

  

  所以答案一定是$m$的一个因子,但是不是每一个因子都能成为答案,玩一下第一个样例就能发现.为什么呢?因为$m$除以$k$之后可能根本组不出$n$个数来...所以要判断一下除出来的数字是否大于$n$.这里不能再枚举质因数了,要枚举约数,两个注意事项:

  ·$1$和$n$都是$n$的约数;

  ·每枚举一个因数就要枚举$m$除以它的那个因数,可以将复杂度降到$\sqrt{M}$

  

 # include <cstdio>
# include <iostream>
using namespace std; int n,m,ans; inline void check (int x) { if(m/x>=n) ans=max(ans,x); } int main()
{
scanf("%d%d",&n,&m);
for (int i=;i*i<=m;++i)
{
if(m%i) continue;
check(i);
check(m/i);
}
printf("%d",ans);
return ;
}

D

Atcoder 水题选做的相关教程结束。

《Atcoder 水题选做.doc》

下载本文的Word格式文档,以方便收藏与打印。