USACO 2025 FEB Platium 题解
T1 - Min Max Subarrays
简要题意
对于长为 \(k\) 的序列 \(x_1, x_2, \cdots, x_k\),你需要交替执行以下两个操作(从第一个操作开始),直到序列中只剩下一个数:
- 将序列中的两个相邻整数替换为它们的较小值。
- 将序列中的两个相邻整数替换为它们的较大值。
定义该序列 \(x_1, x_2, \cdots, x_k\) 的权值为最终余下的整数的最大可能值。
现给定一个长为 \(n\) 的序列 \(a_1, a_2, \cdots, a_n\),请你求出其所有 \(\frac{n(n + 1)}{2}\) 个非空子段的权值之和。
\(2 \leq n \leq 10^6\),\(1 \leq a_i \leq n\)。
首先我们考虑如何计算一个序列的权值。套路地,考虑二分答案 \(x\),将序列中所有小于 \(x\) 的数标记为 \(0\),将所有大于等于 \(x\) 的数标记为 \(1\),那么最终答案大于等于 \(x\) 当且仅当存在一种操作方案使得余下的数为 \(1\)。因此我们将问题转化为:给定一个 0-1 序列 \(a\),求最终余下的数是否可能是 \(1\)。
考虑序列中的 \(k(k \geq 1)\) 个 1 会把序列分成 \(k + 1\) 段极长连续的 0。可能有某些段长度为 \(0\),但我们也把它算上。对于一段连续的 \(0\),若其长度为偶数,则正好会在不消耗 1 的情况下在偶数轮内消完。若其长度为奇数,则在不影响其他连续段的情况下,需要消耗 \(1\) 个 1 才能在偶数轮内消完。因此,最终剩下的数为 1 的条件为:
- 长度为奇数的连续段数严格小于 1 的个数。也就是说,长度为偶数的连续段数至少为 \(2\)。
- 长度为奇数的连续段数至少为 \(3\)。此时我们进行如下操作:\(\texttt{0 1 0 1 0} \rightarrow \texttt{0 1 0 0} \rightarrow \texttt{1 0 0}\),这样我们就能在偶数步内将其中 \(3\) 个奇段 + \(2\) 个 1 转化为 \(2\) 个偶段 + \(1\) 个 1,于是我们就把任意情况转化为了上一种情况。
- 奇段段数至少为 \(2\),且存在任意一个长度至少为 \(3\) 的连续段(不一定是奇段)。分类讨论一下,如果该段是奇段,那么有 \(\texttt{0 1 0 0 0} \rightarrow \texttt{0 1 0 0} \rightarrow \texttt{1 0 0}\);如果该段是偶段,那么有 \(\texttt{0 1 0 1 0 0 0 0} \rightarrow \texttt{0 1 0 1 0 0 0} \rightarrow \texttt{1 0 1 0 0 0} \rightarrow \texttt{1 0 1 0 0} \rightarrow \texttt{1 1 0 0}\)。因此无论如何我们都能在不消耗 \(1\) 的情况下,在偶数步内是奇段的段数减少 \(2\),同样也转化为第一种情况。
剩下的情况数较少且序列的构成都比较简单,容易验证它们剩下的数一定为 \(0\)。因此我们设计出了一个 \(\Theta(n^3 \log V)\) 的算法,可以获得 20 分。
看起来上面的做法难以维护,但其实只要稍微冷静一下就能发现上面的二分是不必要的。
首先是序列长度 \(n\) 为奇数的情况。如果 \(n = 1\),答案是显然的。如果 \(n = 3\),如果最大值为第 \(2\) 个元素那么答案为次大值,否则为最大值。如果 \(n \geq 5\),我们断言答案一定为最大值,因为最大值一定会把序列分成两个长度同奇偶性的段,如果均为偶段那么满足条件 1,如果均为奇段那么满足条件 3。总而言之,当且仅当 \(n = 3\) 且 \(a_2\) 为最大值的时候答案为次大值,否则为最大值。
然后是序列长度 \(n\) 为偶数的情况。首先显然答案不可能是最大值,因为此时最大值会将序列分成一个奇段和一个偶段,不属于上述任意一种情况;答案一定不小于第三大值,因为此时 \(4\) 个段的长度要么为 3 奇 1 偶,属于情况 2,要么为 3 偶 1 奇,属于情况 1。考虑答案什么时候为次大值,容易发现要么分成的 \(3\) 个段均为偶段,要么存在至少一个段长度至少为 \(3\)。综上,当且仅当分成的三个段为 2 奇 1 偶且长度均不超过 \(2\) 时答案为第三大值,否则为次大值。
我们约定一般情况是长度为奇数的序列取最大值,长度为偶数的序列取次大值,那么特殊情况的序列长度不超过 \(6\)。因此只需先把所有序列按一般情况计算一遍,再特判掉所有长度不超过 \(6\) 的序列即可。因此我们现在需要计算的是:所有长度为奇数的子段的最大值之和 + 所有长度为偶数的子段的次大值之和。
从左往右扫描线,用单调栈维护每个数左边第一个不小于它的元素,在维护单调栈的同时维护奇 / 偶项的最 / 次大值的变化量。时间复杂度为 \(\Theta(n)\),但常数可能有点大。
代码
#include <cstdio>
#include <stack>
using namespace std;
const int N=(int)1e6+3;
int n,a[N],to1[N],to2[N]; stack<int> stk;
long long max1[N][2],max2[N][2],max3[N][2],sum1[2],sum2[2],ls1[2],ls2[2];
int read(){
char ch; int x=0;
do ch=getchar();
while(ch<'0'||ch>'9');
while(ch>='0'&&ch<='9')
x=x*10+(ch-'0'),ch=getchar();
return x;
}
int main(){
// freopen("subarray.in","r",stdin);
// freopen("subarray.out","w",stdout);
int i,j,s0,s1,s2,s3,t1,t2,u1,u2;
long long ans=0; n=read(),stk.push(0);
for(i=1;i<=n;i++) a[i]=read();
for(i=1;i<=n;i++){
while(stk.top()&&a[stk.top()]<a[i]) stk.pop();
to1[i]=stk.top(),stk.push(i);
}
for(i=1;i<=n;i++){
ls1[0]=ls1[1]=ls2[0]=ls2[1]=0;
for(j=i-1;j>to1[i];j=to1[j]){
ls1[0]+=max1[j][0],ls1[1]+=max1[j][1];
ls2[0]+=max2[j][0],ls2[1]+=max2[j][1];
}
sum1[0]-=ls1[0],sum1[1]-=ls1[1];
sum2[0]-=ls2[0],sum2[1]-=ls2[1];
if(to1[i]>0){
sum2[0]-=max3[to1[i]][0],max2[to1[i]][0]-=max3[to1[i]][0];
sum2[1]-=max3[to1[i]][1],max2[to1[i]][1]-=max3[to1[i]][1];
while(to2[to1[i]]>to1[to1[i]]&&a[to2[to1[i]]]<a[i]){
s0=to2[to1[i]]/2-to1[to2[to1[i]]]/2;
s1=(to2[to1[i]]+1)/2-(to1[to2[to1[i]]]+1)/2;
sum2[0]-=1ll*s0*a[to2[to1[i]]];
sum2[1]-=1ll*s1*a[to2[to1[i]]];
max2[to1[i]][0]-=1ll*s0*a[to2[to1[i]]];
max2[to1[i]][1]-=1ll*s1*a[to2[to1[i]]];
to2[to1[i]]=to1[to2[to1[i]]];
}
s0=to1[i]/2-to2[to1[i]]/2,s1=(to1[i]+1)/2-(to2[to1[i]]+1)/2;
max3[to1[i]][0]=1ll*s0*a[i],max3[to1[i]][1]=1ll*s1*a[i];
sum2[0]+=max3[to1[i]][0],max2[to1[i]][0]+=max3[to1[i]][0];
sum2[1]+=max3[to1[i]][1],max2[to1[i]][1]+=max3[to1[i]][1];
}
s0=i/2-to1[i]/2,s1=(i+1)/2-(to1[i]+1)/2;
max1[i][0]=1ll*s0*a[i],sum1[0]+=1ll*s0*a[i];
max1[i][1]=1ll*s1*a[i],sum1[1]+=1ll*s1*a[i];
max2[i][0]=ls1[0],sum2[0]+=ls1[0];
max2[i][1]=ls1[1],sum2[1]+=ls1[1];
ans+=sum1[i&1]+sum2[!(i&1)];
to2[i]=i-1,max3[i][i-1&1]=0;
}
for(i=0;i<n;i++){
s1=s2=s3=0,t1=t2=-1;
for(j=i+1;j<=n&&j<=i+6;j++){
if(a[j]>=s1) s3=s2,s2=s1,t2=t1,s1=a[j],t1=j;
else if(a[j]>=s2) s3=s2,s2=a[j],t2=j;
else if(a[j]>s3) s3=a[j];
if(j-i&1){
if(j-i==3&&t1==j-1) ans-=s1-s2;
}else{
u1=min(t1,t2),u2=max(t1,t2);
if((!(u1-i&1)||!(u2-u1&1))&&u1-i<4&&u2-u1<4&&j-u2<3) ans-=s2-s3;
}
}
}
printf("%lld",ans);
// fclose(stdin);
// fclose(stdout);
return 0;
}
T2 - Transforming Pairs
简要题意
对于一个二元组 \((x, y)\),定义一次操作为将其变成 \((x + y, y)\) 或 \((x, x + y)\)。
\(q\) 次询问,每次给定 \(a, b, c, d\),求将 \((a, b)\) 变成 \((c, d)\) 所需的最小操作步数。若无解,输出 \(-1\)。
\(1 \leq q \leq 10^5\),\(-10^{18} \leq a, b, c, d \leq 10^{18}\)。
大力分讨即可。
首先,我们发现将 \((a, b)\) 变成 \((c, d)\) 与将 \((c, -d)\) 变成 \((a, -b)\) 所需的操作次数相同,这样我们能省掉一些情况。
如果 \(a, b\) 同号,不妨设 \(a, b > 0\)。此时 \((a, b)\) 能够变成的二元组必然满足 \(c, d \geq 0\) 且 \(c \neq d\),于是对于变幻过程中的某个二元组 \((c, d)\),它上一步的二元组是唯一确定的:如果 \(c < d\) 则上一步的二元组为 \((c, d - c)\),否则为 \((c - d, d)\)。因此从 \((c, d)\) 逆推到 \((a, b)\) 即可求出所需的操作步数。我们发现这个过程相当于辗转相减,因此只需用辗转相除加速模拟这个过程即可。
如果 \(a = 0\) 或 \(b = 0\),那么下一步必为 \((a, a)\) 或 \((b, b)\),此时即可转化为上一种情况。
如果 \(a, b\) 异号且 \(c, d\) 也异号,或者 \(c, d\) 中至少一个值为 \(0\),只需将这个过程变为 \((c, -d) \rightarrow (a, -b)\) 即可转化为上面两种情况之一。
于是我们只剩下了 \(a, b\) 异号且 \(c, d\) 同号的情况,不妨设 \(a, c, d > 0, b < 0\)。显然有解当且仅当 \(\gcd(a, b) = \gcd(c, d)\),因此我们考虑使用类似双指针的方法同时对两个二元组辗转相减。也就是说,对于从 \((a, b)\) 变到 \((c, d)\) 的过程,我们从两头开始向中间推进变幻的过程。
考虑这两个过程会在什么时候发生重合。设发生重合的时候两个二元组分别为 \((x_1, y_1), (x_2, y_2)\),此时 \(x_1, x_2 > 0, y_2 \geq 0, y_1 \leq 0\)。由于 \((x_1, y_1)\) 可以变成 \((x_2, y_2)\),因此一定满足 \(x_1 = x_2 > 0\),\(y_1 = y_2 = 0\) 或 \(y_2 - y_1 = x_1\)。也就是说,只要在辗转相减的过程中发生了 \(|a| = |c|, |a| \mid |b| + |d|\),就能说明此时发生了重合。
但是辗转相减的复杂度是假的,我们需要用辗转相除来加速这个过程。显然在 \(|b| \leftarrow |b| \bmod |a|, |d| \leftarrow |d| \bmod |c|\) 的过程中不可能产生重合点。对于 \(|a| \leftarrow |a| \bmod |b|, |c| \leftarrow |c| \bmod |d|\) 的过程,不妨令 \(|b|, |d|\) 值较大的二元组进行辗转相除。假设 \(|b| \geq |d|\),由于重合点必然满足 \(|a| \mid |b| + |d| \Rightarrow |a| \leq |b| + |d| \leq 2|b|\),因此可能的重合点只有 \(\Theta(1)\) 个。综上,直接在每一轮辗转相除的过程中暴力枚举可能的重合点即可。
时间复杂度为 \(\Theta(n \log V)\)。
简要题意
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
long long gcd(long long a,long long b,long long& cnt){
return !b?a:gcd(b,a%b,cnt+=a/b);
}
template<typename T=long long>
T read(){
char ch; T x=0; bool bj=0;
do ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'));
if(ch=='-') bj=1,ch=getchar();
while(ch>='0'&&ch<='9')
x=x*10+(ch-'0'),ch=getchar();
return bj?-x:x;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
char a[24]; int n=0,i;
do a[++n]=x%10+'0',x/=10; while(x>0);
for(i=n;i>0;i--) putchar(a[i]);
putchar('\n');
}
int main(){
// freopen("pair.in","r",stdin);
// freopen("pair.out","w",stdout);
int q; long long a,b,c,d,ans,s1,s2;
for(q=read<int>();q>0;q--){
a=read(),b=read(),c=read(),d=read(),ans=0;
if(a==0) swap(a,b),swap(c,d);
if(b==0){
if(a<0) a=-a,c=-c,d=-d;
write((c>0&&d>0&&gcd(c,d,ans)==a||c==a&&d==b)?ans:-1); continue;
}
if(c==0) swap(a,b),swap(c,d);
if(d==0){
if(c<0) a=-a,c=-c; else b=-b;
write((a>0&&b>0&&gcd(a,b,ans)==c||c==a&&d==b)?ans:-1); continue;
}
if(a<0&&b<0) a=-a,b=-b,c=-c,d=-d;
if(a>0&&b>0){
if(c<a||d<b){ puts("-1"); continue; }
while(c>a&&d>b) (c>d)?(ans+=c/d,c%=d):(ans+=d/c,d%=c);
if (a==c&&d>=b&&b%a==d%c) write(ans+d/c-b/a);
else if(b==d&&c>=a&&a%b==c%d) write(ans+c/d-a/b);
else puts("-1"); continue;
}
if((c>0)!=(d>0)){
if(a<0) a=-a,b=-b,c=-c,d=-d;
if(c<0){ puts("-1"); continue; } else b=-b,d=-d;
while(a>c&&b>d) (a>b)?(ans+=a/b,a%=b):(ans+=b/a,b%=a);
if (a==c&&b>=d&&b%a==d%c) write(ans+b/a-d/c);
else if(b==d&&a>=c&&a%b==c%d) write(ans+a/b-c/d);
else puts("-1"); continue;
}
if(c<0) a=-a,b=-b,c=-c,d=-d;
if(a<0) swap(a,b),swap(c,d);
if(gcd(a,b=-b,s1=0)!=gcd(c,d,s2=0)){
puts("-1"); continue;
}
if(a==c&&(b+d)%a==0){
write((b+d)/a); continue;
}
ans+=b/a+d/c,b%=a,d%=c;
if(!b) ans--,b=a;
if(!d) ans--,d=c;
while(true){
if(b<d) swap(a,c),swap(b,d);
if((s1=a%b+b*2)<=min(a,c)&&c%d==s1%d&&(b+d)%s1==0){
ans+=(a-s1)/b+(c-s1)/d+(b+d)/s1; break;
}
if((s1=a%b+b)<=min(a,c)&&c%d==s1%d&&(b+d)%s1==0){
ans+=(a-s1)/b+(c-s1)/d+(b+d)/s1; break;
}
if((s1=a%b)<=min(a,c)&&c%d==s1%d&&(b+d)%s1==0){
ans+=(a-s1)/b+(c-s1)/d+(b+d)/s1; break;
}
ans+=a/b,a%=b; if(!a) ans--,a=b;
ans+=b/a,b%=a; if(!b) ans--,b=a;
}
write(ans);
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
T3 - True or False Test
简要题意
你正在参加一场 \(n\) 道题的考试。对于第 \(i\) 道题,如果答对了就会获得 \(a_i\) 分,如果答错了就会失去 \(b_i\) 分,如果不回答那么分数就不变。
你非常聪明,因此你知道所有题目的答案,但你担心阅卷老师会在测试后修改至多 \(k\) 道题的答案,使得你无法答对这些题目。
给定 \(q\) 个 \(k\) 的候选值,你需要对每一个 \(k\) 求出在回答至少 \(k\) 道题的前提下,至多可以保证得到多少分。
\(1 \leq n \leq 2 \times 10^5\),\(1 \leq q \leq n + 1\),\(1 \leq a_i, b_i \leq 10^9\)。
询问是假的,事实上你需要对每一个 \(k\) 都求出答案 \(\mathrm{res}(k)\)。
首先,如果你已经确定了要做哪道题,那么老师需要怎么办才能让你的得分最小呢?显然老师应该让你的得分减少得尽可能多,因此老师一定会选你做的题中 \(a_i + b_i\) 最大的 \(k\) 道题改变答案。因此我们不妨将所有题按 \(a_i + b_i\) 从大到小排序,那么你做的 \(x\) 道题中前 \(k\) 道会造成 \(-b_i\) 的贡献,后 \(x - k\) 道会造成 \(a_i\) 的贡献。
对于一次询问 \(k\),考虑枚举负贡献题目与正贡献题目的分界线 \(i\),那么你需要在 \([1, i]\) 中选择恰好 \(k\) 道题,在 \([i + 1, n]\) 中选择任意多道题;贪心地,我们肯定要把 \([i + 1, n]\) 中的题全部选上,在 \([1, i]\) 中应选择 \(b\) 最小的 \(k\) 道题。也就是说,记 \(S(i)\) 表示序列 \(a\) 的前缀和,记 \(f(k, i)\) 表示“\([1, i]\) 中最小的 \(k\) 个 \(b\) 值之和 + S(i)”的最小值,那么这次询问的答案即为 \(S(n) - \min_{i = k}^n f(k, i)\)。也就是说,我们需要对每个 \(k\) 求出 \(f(k, i)\) 的最小值。
这里有一个很重要的性质,也是本题的关键,就是对于一个固定的 \(k\),\(f(k, i) - f(k - 1, i)\) 是单调不增的。原因很简单,考虑 \(f(k, i) - f(k - 1, i)\) 表示的是 \([1, i]\) 中第 \(k\) 小的 \(b\) 值,而 \(f(k, i + 1) - f(k - 1, i + 1)\) 表示的是 \([1, i + 1]\) 中第 \(k\) 小的 \(b\) 值,因此必有 \(f(k, i) - f(k - 1, i) \geq f(k, i + 1) - f(k - 1, i + 1)\)。从另一个方面来说,\(f\) 满足四边形不等式。
由四边形不等式我们可以导出决策单调性,也就是说,记 \(g(k)\) 表示使得 \(f(k, i)\) 取最小值的 \(i\) 是多少,那么 \(g(k)\) 是单调不减的。原因也很简单,对于 \(i < g(k)\),由于 \(f(k + 1, i) - f(k, i) \geq f(k + 1, g(k)) - f(k, g(k))\) 且 \(f(k, i) \geq f(k, g(k))\),因此 \(f(k + 1, i) \geq f(k + 1, g(k))\),进而有 \(g(k + 1) \geq g(k)\)。因此,\(f(k, i)\) 满足决策单调性。
既然 \(f(k, i)\) 满足决策单调性,那么我们直接使用分治就可以了。使用主席树来维护“\([1, i]\) 中最小的 \(k\) 个 \(b\) 值之和”,时间复杂度为 \(\Theta(n \log^2 n)\)。
代码
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
const int N=200003,log_N=19;
const long long PIN=1e18;
struct Pair{
long long x,y;
bool operator<(const Pair p)const{
return x+y>p.x+p.y;
}
}a[N];
int n,root[N],len=0;
long long res[N],sum[N],alls[N];
int binary(long long x){
int l=1,r=len+1;
while(l<r){
int mid=(l+r)/2;
(alls[mid]>=x)?(r=mid):(l=mid+1);
}
return r;
}
namespace SGT{
struct Node{
int lson,rson,l,r,cnt; long long sum;
}node[N*(log_N+3)];
int len_node=0;
int make_node(int l,int r){
node[++len_node]={0,0,l,r,0,0};
return len_node;
}
int copy(int u){
node[++len_node]=node[u];
return len_node;
}
int build(int l,int r){
int u=make_node(l,r);
if(l==r) return u;
int mid=(l+r)/2;
node[u].lson=build(l,mid );
node[u].rson=build(mid+1,r);
return u;
}
void modify(int u,int v,int k,long long x){
node[u].cnt++,node[u].sum+=x;
if(node[u].l<node[u].r){
int mid=(node[u].l+node[u].r)/2;
if(k<=mid){
if(node[u].lson==node[v].lson)
node[u].lson=copy(node[v].lson);
modify(node[u].lson,node[v].lson,k,x);
}else{
if(node[u].rson==node[v].rson)
node[u].rson=copy(node[v].rson);
modify(node[u].rson,node[v].rson,k,x);
}
}
}
long long query(int u,int k){
int cnt=0; long long ans=0;
if(node[u].cnt<=k) return node[u].sum;
while(node[u].l<node[u].r)
if(cnt+node[node[u].lson].cnt<=k){
cnt+=node[node[u].lson].cnt;
ans+=node[node[u].lson].sum;
u=node[u].rson;
}else u=node[u].lson;
return ans+(k-cnt)*alls[node[u].l];
}
}
void solve(int l,int r,int bl,int br){
long long s1; if(l>r) return ;
int mid=(l+r)/2,i,minu=-1;
for(res[mid]=PIN,i=max(bl,mid);i<=br;i++){
s1=SGT::query(root[i],mid)+sum[i];
if(s1<res[mid]) res[mid]=s1,minu=i;
}
solve(l,mid-1,bl,minu);
solve(mid+1,r,minu,br);
}
int read(){
char ch; int x=0;
do ch=getchar();
while(ch<'0'||ch>'9');
while(ch>='0'&&ch<='9')
x=x*10+(ch-'0'),ch=getchar();
return x;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
char a[24]; int n=0,i;
do a[++n]=x%10+'0',x/=10; while(x>0);
for(i=n;i>0;i--) putchar(a[i]);
putchar('\n');
}
int main(){
// freopen("trufalse.in","r",stdin);
// freopen("trufalse.out","w",stdout);
int i,q; n=read(),q=read();
for(i=1;i<=n;i++)
a[i].x=read(),a[i].y=read();
sort(a+1,a+n+1);
for(i=1;i<=n;i++){
alls[++len]=a[i].y;
sum[i]=sum[i-1]+a[i].x;
}
sort(alls+1,alls+len+1);
len=unique(alls+1,alls+len+1)-alls-1;
root[0]=SGT::build(0,len);
for(i=1;i<=n;i++){
root[i]=SGT::copy(root[i-1]);
SGT::modify(root[i],root[i-1],binary(a[i].y),a[i].y);
}
solve(0,n,0,n);
for(i=1;i<=q;i++) write(sum[n]-res[read()]);
// fclose(stdin);
// fclose(stdout);
return 0;
}