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;
}
posted @ 2025-03-05 21:23  kilomiles  阅读(56)  评论(1)    收藏  举报