[ROIR 2022] 礼物 (Day 2) 题解

[ROIR 2022] 礼物 (Day 2) 题解


知识点

链表。

分析

首先看到数据范围 \(k\le\min(100,n)\),说明复杂度与 \(k\) 有关,那么我们就可以考虑暴力一点地解决这个问题。

一开始我们可以想到按顺序插入数值,这样会有较好的性质,比如从大到小插入一个 \(u\) 时,包含 \(u\) 和它之前插入的值作为区间前 \(k\) 大的情况其实只有 \(k\) 种。

假设 \(k=2\),以 \(1/0\) 分别代表插入和没插入的值,\(\color{red}1\) 表示我们这次插入的值:

\[000{\color{blue}1}0000{\color{red}1}00000000{\color{green}1}000010000 \]

那么其实可以分为两种情况:

  1. 包括 \({\color{blue}1}\)\({\color{red}1}\)
  2. 包括 \({\color{red}1}\)\({\color{green}1}\)

我们可以考虑用求前缀和数组区间 \(\min,\max\) 优化,那么一次只要遍历 \(O(k)\) 就可以解决。

但是这样实现较为麻烦,插入需要 set<int> 之类的,维护区间 \(\min,\max\) 需要 ST 表之类的,我们可以转化问题:把从大到小插入改为从小到大删除。

那么删除只需维护链表,维护区间 \(\min,\max\) 也可以直接在链表上一起维护,代码变得非常简洁,复杂度 \(O(nk)\)

最后,注意恶心的 \(k=0\)

代码

constexpr int N(2e5+10);

int n,m;
int a[N],idx[N],pre[N],nxt[N];
ll ans;
ll mn[N],mx[N],sum[N];

signed main() {
	/*DE("Input");*/
	scanf("%d%d",&n,&m);
	FOR(i,1,n)scanf("%d",&a[i]);
	FOR(i,1,n)sum[i]=sum[i-1]+a[i];
	if(!m) {
		ans=-LINF;
		ll mn(0);
		FOR(i,1,n)tomax(ans,sum[i]-mn),tomin(mn,sum[i]);
		printf("%lld\n",ans);
		return 0;
	}
	/*DE("Init");*/
	FOR(i,1,n)idx[i]=i;
	sort(idx+1,idx+n+1,[](int x,int y) { return a[x]<a[y]; });
	pre[0]=0,nxt[0]=1,pre[n+1]=n,nxt[n+1]=0;
	FOR(i,1,n)pre[i]=i-1,nxt[i]=i+1;
	FOR(i,0,n)mn[i]=mx[i]=sum[i];
	/*DE("Solve");*/
	FOR(i,1,n-m+1) {
		const int &u(idx[i]);
		int l(u);
		for(int j(m); l&&j>0; l=pre[l],--j);
		int r(l);
		ll cnt(0);
		for(int j(m); j>0; cnt+=a[r=nxt[r]],--j);
		for(; l<=u&&r<=n; cnt-=a[l=nxt[l]],cnt+=a[r=nxt[r]])tomax(ans,mx[r]-mn[l]-cnt);
		tomin(mn[pre[u]],mn[u]),tomax(mx[pre[u]],mx[u]);
		pre[nxt[u]]=pre[u],nxt[pre[u]]=nxt[u];
	}
	/*DE("Output");*/
	printf("%lld\n",ans);
	return 0;
}
posted @ 2025-11-23 15:02  Add_Catalyst  阅读(1)  评论(0)    收藏  举报