USACO 2024 January Contest Bronze 铜组题解
发表于|更新于
|阅读量:
前言
感觉这次难度完全没有去年 12 月的铜组高,那个时候刚学三个月看到那几个题直接爆零,这次直接在研学路上用手机 ak 了。。
T1 - Majority Opinion
题意
给定一个长度为 N 的序列 h,每次操作可以选择一个 h 的连续子串,并且将其中的元素全部修改成子串中出现次数最多且超过其长度一半的元素,输出在经过若干次操作后,可以让哪些元素 hi 成为序列中唯一一种元素。
思路
看到 N 的数据范围是 2×105,我们首先思考 O(N) 的算法。
很容易想到,序列 h 中存在三个元素,其中有两个相同的 hi(即序列中存在两个距离相隔不超过一个元素的 hi) 是 hi 满足题面要求的充分条件,因为我们可以先把这三个元素全部修改为 hi,然后就可以一点点将 hi 扩展到整个 h 序列里。
以下是充要条件的证明:
做法证明
假设 h 中距离相隔最短的两个 hi 之间间隔为两个元素或以上。
那么一个包含最多 hi 的子串必然是以下形式:
hi,x,x,hi,x,x,hi
即头、尾均为 hi,每两个 hi 相隔两个元素,子串长度 l=3k+1(k∈N+)。
此时子串里包含的 hi 数量为 3l−1+1=3l+2=62l+4,而题面中的要求数量最少为 ⌈2l⌉=2l+1,由于 l≥4,解不等式可得该子串中的 hi 数量一定不满足题目要求。
实现
在循环里对每个 hi 扫一遍,判断是否出现 hi−1=hi+1,hi=hi−1 或 hi=hi+1 的情况,如果出现就用一个 map 记录下来,然后排序输出即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| int h[200005]; int d[200005]; map<int,bool> f; map<int,bool> vis; void solve(){ f.clear();vis.clear(); int n;read(n); for(int i=1;i<=n;i++){read(a[i]);} a[0] = -1,a[n+1] = -1; bool nf = 0; for(int i=1;i<=n;i++){ if(a[i-1]==a[i] || a[i+1]==a[i]){ nf = 1; f[a[i]] = 1; } if(a[i-1]==a[i+1]){ nf = 1; f[a[i-1]] = 1; } } if(nf){ int cnt = 0; for(int i=1;i<=n;i++){ if(f[a[i]] && !vis[a[i]]){ cnt++; d[cnt] = a[i]; vis[a[i]] = 1; } } sort(d+1,d+1+cnt); for(int i=1;i<=cnt;i++){ cout<<d[i]; if(i!=cnt) cout<<" "; } } else cout<<-1; cout<<"\n"; }
int main(){ int t = 1; read(t); for(int tt=1;tt<=t;tt++){ solve(); } }
|
T2 - Cannonball
题意
给定一个长度为 N 的序列和 Bessie 的起始位置 S,Bessie 在初始状态下在序列的 S 处,能量为 1,方向向右。Bessie 每次进行弹跳会向前跳 k 个单位。
如果 Bessie 跳到的位置 i 是跳板,Bessie 的能量会增加 vi;如果位置 i 是目标,且 Bessie 的能量大于 qi,则 Bessie 可以击破该目标,否则不能击破。目标不会改变她的方向或能量值。
输出在 Bessie 离开序列范围内或在序列内无限弹跳之前,她能击破多少个目标。
思路
按照题意模拟即可。
终止的条件有两种,第一种自然是 Bessie 跳到 ≤0 或 ≥N 的位置上,第二种则是在跳到同一个目标上时,Bessie 面朝的方向、能量值均相同,会陷入死循环。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| int n,s; int t[100005],v[100005],tg[100005];
map<int,bool> vis[100005]; int ans = 0; void search(int p,int x,int d){ int fp = p; if(t[p]){ if(x>=v[p]) tg[p] = 1; if(vis[p][x*d]) return; vis[p][x*d] = 1; } else{ x+=v[p]; d = -d; } fp = p+d*x; if(fp<=0 || fp>n) return; else search(fp,x,d); } void solve(){ cin>>n>>s; reset(tg,0); for(int i=1;i<=n;i++){ cin>>t[i]>>v[i]; } search(s,1,1); for(int i=1;i<=n;i++) ans+=tg[i]; cout<<ans; } int main(){ for(int tt=1;tt<=1;tt++){ solve(); } }
|
T3 - Balancing Bacteria
题意
给定一个长度为 N 的序列 a,可以进行如下操作:
- 选定一个值 L(1≤L≤N)
- 对序列进行两种修改之一,使得 aN←aN+L,aN−1←aN+(L−1),⋯,aN−L+1←aN−L+1+1 或 aN←aN−L,aN−1←aN−(L−1),⋯,aN−L+1←aN−L+1−1。
求进行若干次操作,使得序列 a 中的元素全部变为 0 的最少次数。
思路
想让序列全部归零,必须从第 1 个元素开始修改,否则如果从第 i 个元素进行修改,那后续再对 [1,i) 之间的元素进行修改就会影响到 i 的值。
所以我们的修改流程就是首先选定 L=N,进行 ∣a1∣ 次操作使 a1 归零,然后选定 L=N−1,进行 ∣a2∣ 次操作后使 a2 归零(注意此时 a2 的值是已经被修改过的),最后选定 L=1,进行 aN 次操作后序列就全部归零了。
现在的问题就是求出第 i 次修改前 ai 的值是多少,设第 i 次修改前 ai 的值是 di,操作次数总和即为 ∑i=1N∣di∣。
下面就是愉快的推式子环节。
递推方法
显然 d1=a1,因为第一个元素是第一个被修改的。
在进行完 ∣d1∣ 次 L=N 的操作后,a1 的值变为 0,也就是减小了 d1,则 a2 被修改为 d2−d1×2=a2−a1×2,ai 被修改为 di−d1×i=ai−a1×i。
同理,在进行完 ∣d2∣ 次 L=N−1 的操作后,a2 的值变为 0,也就是减小了 d2,则 a3 被修改为 d3−d2×2=a3−d1×3−d2×2,ai 被修改为 di−d2×2=ai−a1×i−a2×i−1。
于是在上面对过程的模拟后,我们可以得到如下公式:
di=ai−2×ai−1−3×ai−2−⋯−i×a1
但好像没有什么方法能直接算出这个式子的值,于是考虑用递推的方法去做,找一找 di 和 di−1 的关系:
将式子后一大段的值设为 gi,即 di=ai+gi,则:
gi−1=−2×ai−2−3×ai−3−⋯−(i−1)×a1
gi=−2×ai−1+(−2×ai−2−3×ai−3−⋯−(i−1)×a1)−∑j=1i−2aj
=−2×ai−1+gi−1−∑j=1i−2aj
于是我们的递推关系就求出来了,至于后面的 ∑j=1i−2aj 用前缀和求出即可,初始状态为 g1=0 且 d1=ai+g1=a1。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const int N = 200005; ll a[N],d[N],pf[N],g[N]; void solve(){ int n;cin>>n; for(int i=1;i<=n;i++){cin>>a[i];} d[1] = a[1];g[1] = 0;pf[0] = 0;pf[1] = a[1]; for(int i=2;i<=n;i++){ g[i] = 2*d[i-1]+g[i-1]+pf[i-2]; d[i] = a[i]-g[i]; pf[i] = pf[i-1]+d[i]; } ll sum = 0; for(int i=1;i<=n;i++){ sum+=abs(d[i]); } cout<<sum; }
int main(){ int t = 1;
for(int tt=1;tt<=t;tt++){ solve(); } }
|