专栏文章

题解:P12446 [COTS 2025] 答好位 / Vrsta

P12446题解参与者 3已保存评论 3

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
3 条
当前快照
1 份
快照标识符
@mipcna76
此快照首次捕获于
2025/12/03 09:49
3 个月前
此快照最后确认于
2025/12/03 09:49
3 个月前
查看原文
挑战最短代码。
显然,原问题等价于求出每个区间的次大值下标。于是我们可以打表观察区间次大值的结构。
例如,当 a=[2,8,1,5,9,6,3,7,4]a=[2,8,1,5,9,6,3,7,4] 时,我们可以打出以下表:(第 ii 行第 jj 列表示区间 [i,j][i,j] 的次大值下标,若不存在则为 00)。
CPP
0 1 1 4 2 2 2 2 2
0 0 3 4 2 2 2 2 2
0 0 0 3 4 6 6 8 8
0 0 0 0 4 6 6 8 8
0 0 0 0 0 6 6 8 8
0 0 0 0 0 0 7 6 6
0 0 0 0 0 0 0 7 9
0 0 0 0 0 0 0 0 9
0 0 0 0 0 0 0 0 0
观察发现,每个元素最多占据两个矩形区域。下文中用 [a,b]×[c,d][a,b]\times [c,d] 的形式表示一个矩形区域。
证明:
考虑一个元素何时成为区间次大值。
preipre_i 表示 ii 前面最后一个比 aia_i 大的位置,prepreiprepre_i 表示 ii 前面倒数第二个比 aia_i 大的位置;nxtinxt_i 表示 ii 后面最前一个比 aia_i 大的位置,nxtnxtinxtnxt_i 表示 ii 后面第二个比 aia_i 大的位置。
ii 成为次大值的区域为:
[preprei+1,prei]×[i,nxti1][prei+1,i]×[nxti,nxtnxti1][prepre_i+1,pre_i]\times [i,nxt_i-1]\cup [pre_i+1,i]\times [nxt_i,nxtnxt_i-1]
于是我们定位到每个矩形即可。
考虑递归进行这个过程。如果我们知道 [l,r][l,r] 内最大值下标为 mxmx,次大值下标为 sese,不妨令 mx<semx<se
此时我们可以提取出矩形 [l,mx]×[se,r][l,mx]\times [se,r],往 [l,se1],[mx+1,r][l,se-1],[mx+1,r] 递归即可。
递归时进行记忆化,则每个矩形恰好被递归一次,总递归次数为 2n2n
除第一次外区间的最大值下标可以在递归时传下去。第一次使用 nn 次询问查询即可。总次数为 3n3n
code:
CPP
#include<bits/stdc++.h>
#define rep(i,j,k,...) for(int i=j;i<=k;i++)
using namespace std;

const int maxn=2070;

int a[maxn],n;
int ans[maxn][maxn];

int ask(int l,int r){
	cout<<"? "<<l<<" "<<r<<endl;
	int x;
	cin>>x;
	return x;
}

bool vis[maxn][maxn];

void solve(int l,int r,int mx){
	if(l==r) return;
	if(vis[l][r]) return;
	vis[l][r]=1;
	int se=ask(l,r),x,y;
	tie(x,y)=minmax(mx,se);
	rep(i,l,x) rep(j,y,r) ans[i][j]=se;
	solve(l,y-1,x),solve(x+1,r,y);
}

signed main(){
	cin>>n;
	int x=ask(1,n),l=x,r=x;
	while(r!=n&&ask(l,r+1)!=x) r++;
	while(l!=1&&ask(l-1,r)!=x) l--;
	int pos=(l==1? r+1: l-1);
	solve(1,n,pos);
	cout<<"!"<<endl;
	int q;
	cin>>q;
	while(q--){
		int l,r;
		cin>>l>>r;
		cout<<ans[l][r]<<endl;
	}
}

评论

3 条评论,欢迎与作者交流。

正在加载评论...