Atcoder Grand Contest 048 解题报告 (A-D)
Pro A atcoder<S
-
题意:给定字符串 \(s\) ,每次只能交换 \(s\) 中的相邻两个字符,求最小的交换次数使得 \(s>\)
'atcoder'
。如果无解输出-1
。多组数据。 -
数据范围:\(T\leq 100,|s|\leq 10^3\)
-
做法:
首先判掉无解( \(s\) 中全为 \(a\) )和已经满足条件的情况。在 \(s\) 中找到第一个不是 \(a\) 的字符,设其为 \(x\) ,若 \(x>t\) ,那么直接将 \(x\) 移到 \(t\) 的位置;否则移到 \(a\) 的位置。 可以证明这样是最优的(分类讨论以下即可)。
-
代码:
#include <bits/stdc++.h> const int N=1e3+5; using namespace std; const char g[]={'a','t','c','o','d','e','r','\0'}; char s[N]; int l; bool cant(){ for(int i=0;i<l;++i) if(s[i]!='a') return false; return true; } bool done(){ for(int i=0;i<min(l,7);++i) if(s[i]>g[i]) return true; else if(s[i]<g[i]) return false; return l>7; } void solve(){ l=strlen(s); if(cant()) {puts("-1");return;} if(done()) {puts("0"); return;} for(int i=0;i<l;++i){ if(s[i]=='a') continue; if(s[i]<='t') {printf("%d\n",i);break;} else {printf("%d\n",i-1);break;} } } int main(){ int T;scanf("%d",&T); while(T--) scanf("%s",s),solve(); return 0; }
Pro B Bracket Score
-
题意:给定 \(n\) 个变量,每个变量可以是
(
、)
、[
、]
中的一种。变量 \(i\) 为()
中的一种时有 \(a_i\) 贡献,否则有 \(b_i\) 贡献。当 \(n\) 个变量组成的字符串为合法字符串时,求最大的 \(n\) 个变量的贡献和。 -
数据范围:\(n\leq 10^5\) ,\(n\) 是偶数
-
做法:
假设最后有 \(k\) 个位置是
[]
,分别为 \(x_1,x_2,\cdots,x_k\) 。 我们假设 \((x_1,x_2),\cdots,(x_{k-1},x_k)\) 配对且对于每一对配对关系恒有 \(x_{pre}<x_{suf}\) 。 \(x_2-x_1-1\) 必然是偶数,即 \(x_1\) 和 \(x_2\) 的奇偶性不同。同理,那么 \(x_1,x_2,\cdots,x_k\) 这 \(k\) 个数中必然恰好有 \(\frac{k}{2}\) 个奇数, \(\frac{k}{2}\) 个偶数。 实际上,恰好半奇半偶也正是 \(x_1,x_2,\cdots,x_k\) 合法的充分条件。我们可以通过构造方案来证明:
- 我们一定可以找到一个奇数 \(x_i\) 和一个偶数 \(x_j\) 满足两者之间再无 \(x\) 。
- 我们可以将 \(x_i\) 和 \(x_j\) 配对并消去,这时 \(x\) 序列仍然满足半奇半偶的性质。
- 重复上述两步即可。
问题转化为找到一种 \(x_1,x_2,\cdots,x_k\) 满足半奇半偶的使得贡献最大的方案。假设选择
()
视作选 \(0\) ,[]
视作选 \(1\) ,实际上,每个 \(x_1,x_2,\cdots,x_k\) 满足半奇半偶的方案都对应着一种选 \(\frac{n}{2}\) 个 \(0\) 和 \(\frac{n}{2}\) 个 \(1\) 的方案——这是一种“双射”关系。为什么呢?- 假设在某一种选 \(0\) 的位置为半奇半偶的方案下我们选了 \(c_0\) 个 \(0\) ,\(c_1\) 个 \(1\) 。\(c_0+c_1=1\) 。
- \(0\) 的位置是半奇半偶的同时 \(1\) 的位置也必然是半奇半偶。
- 我们将偶数位全部取反。此时 \(0\) 的个数为 \(c_0-\frac{c_0}{2}+\frac{c_1}{2}=\frac{n}{2}\) 。正好。
问题转化为找到一种选择 \(\frac{n}{2}\) 个 \(0\) 和 \(\frac{n}{2}\) 个 \(1\) 使得贡献最大的方案。先把 \(b_i\) 全部选择了,再把 \(a_i-b_i\) 排序后选择前 \(\frac{n}{2}\) 大即可。当然,在这之前要记住在偶数位交换 \(a,b\) 。
-
代码:
const int N=1e5+5; using namespace std; long long n,ans; long long a[N],b[N]; int main(){ r(n); for(int i=1;i<=n;++i) r(a[i]); for(int i=1;i<=n;++i) r(b[i]); for(int i=2;i<=n;i+=2) swap(a[i],b[i]); for(int i=1;i<=n;++i) ans+=a[i],b[i]-=a[i]; sort(b+1,b+n+1,greater<int>() ); for(int i=1;i<=n/2;++i) ans+=b[i]; w(ans); return 0; }
Pro C Penguin Skating
-
题意:有 \(n\) 只企鹅, \(m\) 个广场,初始时每只企鹅在一个广场上,且任意时刻一个广场上只有 \(1\) 只企鹅。你可以让某只企鹅向左滑或者向右滑。若让企鹅 A 向左滑,当 A 的左边没有企鹅时,它会滑倒广场 \(1\) ;否则假设在 A 左边距离 A 最近的企鹅在广场 \(x\) ,那么他就会滑到 \(x+1\) 。向右滑同理。给定 \(n\) 个企鹅的初始位置以及目标位置,问从初始位置变成目标位置的最少次数。无解输出
-1
。 -
数据范围:\(n\leq 10^5,m\leq 10^9\)
-
做法:
设 \(\{a\}\) 表示企鹅的初始位置, \(\{b\}\) 表示目标位置。设 \(a_0=0,a_{n+1}=m+1,d_i=a_i-a_{i-1}-1\) ,也就是记录企鹅两两之间的距离。再设 \(b_0=0,b_{n+1}=m+1,e_i=b_i-b_{i-1}-1\) 。当 \(\forall i\in [1,n+1] \text{ s.t. } d_i=e_i\) 时,由于初始/结束位置均确定,且两两之间差值也确定,那么实际上原序列也就确定相同了。每次滑动企鹅,实际上就是 \(d_{i+1 \text{ or }i-1}-=d_i,d_i=0\) 的变换。贪心凑出来每一项 \(e_i\) 即可。
-
代码:
const int N=1e5+5; using namespace std; int n,L; long long a[N],b[N]; int main(){ r(n),r(L); for(int i=1;i<=n;++i) r(a[i]); for(int i=1;i<=n;++i) r(b[i]); a[n+1]=L+1,b[n+1]=L+1; for(int i=n+1;i>=1;--i) a[i]=a[i]-a[i-1]-1,b[i]=b[i]-b[i-1]-1; long long ans=0,j=1,st=1; for(int i=1;i<=n+1;++i){ if(!b[i]) continue; while(!a[j]) ++j;st=j; long long cur=0; while(cur<b[i]&&j<=n+1) cur+=a[j++]; if(cur!=b[i]) {puts("-1");return 0;} ans+=max(0ll,j-1-i)+max(0ll,i-st); } w(ans); return 0; }
Pro D Pocky Game
-
题意:有 \(n\) 堆石子,两个玩家轮流丢石子。1 号玩家每次只能从最左边的石子堆丢,2 号玩家每次只能从最右边的石子堆丢。每次可以在当前堆中丢弃任意个石子。无法操作的人输。给定初始的石子序列 \(\{a\}\) ,判定谁能赢。
-
数据范围:\(n\leq 10^3,a_i\leq 10^9\)
-
做法:
首先有一个性质要把握:
- 一个人每次要么拿一个,要么拿一堆。因为拿一个后的决策范围包含了拿 \(k(k\in(1,a_i))\) 个的决策范围。
有了这个性质后,设
one[L][R]
表示目前只有 \([L,R]\) 这些石子堆,\(1\) 号先手时,\(a_L\) 至少为多少才能让\(1\) 号必胜。同理设two[L][R]
。- 当 \(a_R<two[L+1][R]\) 时。无论第 \(L\) 堆石子有多少个, 1 号直接取完就赢了。所以 \(one[L][R]=1\) 。
- 当 \(a_R\ge two[L+1][R]\) 时。双方肯定都会希望自己先把对方耗尽,所以均会一个一个取。假设第 \(L\) 堆有 \(x\) 个石子。当 1 号取到第 \(L\) 堆只剩 \(one[L][R-1]-1\) 个时,2 号必然一把将第 \(R\) 堆全取完;当 2 号取到第 \(R\) 堆只剩 \(two[L+1][R]-1\) 个时,1 号必然一把将第 \(L\) 堆全取完。 所以可得
\[x-one[L][R-1]+1>a_R-two[L+1][R]+1\\ x>a_R-two[L+1][R]+one[L][R-1] \] 所以 \(x\) 的最小值,也就是 \(one[L][R]=a_R-two[L+1][R]+one[L][R-1]\) 。求
two[][]
的过程类似。我们可以在 \(\mathcal{O}(n^2)\) 的时间内解决此题。 -
代码:
const int N=1e3+5; using namespace std; int n; long long a[N],L[N][N],R[N][N]; void solve(){ r(n); for(int i=1;i<=n;++i) r(a[i]),L[i][i]=R[i][i]=1; for(int k=2;k<=n;++k) for(int i=1;i+k-1<=n;++i){ int j=i+k-1; if(a[j]<R[i+1][j]) L[i][j]=1; else L[i][j]=a[j]-R[i+1][j]+L[i][j-1]+1; if(a[i]<L[i][j-1]) R[i][j]=1; else R[i][j]=a[i]-L[i][j-1]+R[i+1][j]+1; } puts(a[1]>=L[1][n]?"First":"Second"); } int main(){ int T;r(T); while(T--) solve(); return 0; }