Pro A atcoder<S
-
题意:给定字符串 (s) ,每次只能交换 (s) 中的相邻两个字符,求最小的交换次数使得 (s>)
'atcoder'
。如果无解输出-1
。多组数据。 -
数据范围:(Tleq 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',' '}; 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 ",i);break;} else {printf("%d ",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) 个变量的贡献和。 -
数据范围:(nleq 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
。 -
数据范围:(nleq 10^5,mleq 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 iin [1,n+1] ext{ s.t. } d_i=e_i) 时,由于初始/结束位置均确定,且两两之间差值也确定,那么实际上原序列也就确定相同了。每次滑动企鹅,实际上就是 (d_{i+1 ext{ 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}) ,判定谁能赢。
-
数据范围:(nleq 10^3,a_ileq 10^9)
-
做法:
首先有一个性质要把握:
- 一个人每次要么拿一个,要么拿一堆。因为拿一个后的决策范围包含了拿 (k(kin(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_Rge 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; }