[ZROI2021]Unfair Tournament

题目

题目描述
有一场不公平的竞赛,一共有 n n n 位参赛选手,站成一列,第 i i i 位参赛选手的初始实力值为 a i a_i ai

一共有 n − 1 n−1 n1 场比赛,每场比赛你可以任意选择两个相邻的参赛选手,实力值大的胜出(若两个人实力相同则你可以指定胜者)。这看上去很公平。糟糕的是,由于战斗经验的增加,胜者的实力值会永久加一。败者离开这场比赛(从这个选手序列中删除)。在这 n − 1 n−1 n1 场比赛后剩下的那一名选手即为冠军。

容易看出冠军不一定是初始实力值最大的选手。求哪些选手可能是最后的冠军。

数据范围与提示
n ≤ 5 × 1 0 5 ,    0 ≤ a i < 2 31 n\le 5\times 10^5,\;0\le a_i<2^{31} n5×105,0ai<231

考场草稿

怎样保护一个选手?把他身边比他弱的都干掉。不能让强强对决,这样只会造出一个更强的!也不能弱弱对决,那样会让这个人少一些经验。

假如他会失败,一定是身边两个人把他夹住了。即存在 l < x < r l<x<r l<x<r 使得 r − l − 2 + a x < min ⁡ ( a l , a r ) r-l-2+a_x<\min(a_l,a_r) rl2+ax<min(al,ar),即 r − l − 1 + a x ≤ min ⁡ ( a l , a r ) r-l-1+a_x\le\min(a_l,a_r) rl1+axmin(al,ar)

如果 a l < a r a_l<a_r al<ar,那么只需要 a x ≤ a l + l − r + 1 a_x\le a_l+l-r+1 axal+lr+1,最小化 r r r 并且最大化 a l + l a_l+l al+l 即可。

如果 a l ≥ a r a_l\ge a_r alar,只需要 a x ≤ a r − r + l + 1 a_x\le a_r-r+l+1 axarr+l+1,最大化 l l l 并且最大化 a r − r a_r-r arr 即可。

能不能 c d q \tt cdq cdq 呢?考虑将序列分成 [ 1 , m i d ) , [ m i d , r ) [1,mid),[mid,r) [1,mid),[mid,r),然后左边的 a l a_l al 贡献给更大的 a r a_r ar 。有了!在 a r a_r ar 处考虑它贡献给 [ m i d , r ) [mid,r) [mid,r),就只需要求最大的 a l + l a_l+l al+l 即可。 l l l 同理。

按照 a a a 归并即可。 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

一些解释

为什么会往 c d q \tt cdq cdq 上面想呢?因为这个 l < x < r l<x<r l<x<r 的条件中, l l l 的范围相差甚小。很久之前我就说过,这种东西就往 c d q \tt cdq cdq 上面靠准没错。

当然,也有可能是因为 l < r l<r l<r a l < a r a_l<a_r al<ar 类似二维偏序吧?因为 min ⁡ \min min 这玩意儿也挺难搞的。要么 min ⁡ - max ⁡ \min\text-\max min-max 反演,要么枚举,再没别的想法。

上面那个等价条件是怎么想到的呢?我也不知道 😕 可能是因为这种题目必须要等价转化吧。因为它本身是一个很复杂的过程,你去模拟它就是 O ( n 2 ) \mathcal O(n^2) O(n2) 的了……

总结一下就是:要先想到 c d q \tt cdq cdq再考虑怎么套。上一次见到这种先想算法再套进去的题目,已经是上古时期了……

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 500005;
const int_ INF = (1ll<<60)-1;

struct Ren{
	int pos; int_ val;
	bool operator < (const Ren &p) const {
		return val < p.val;
	}
};
Ren ren[MaxN]; int ans[MaxN];

Ren xyx[MaxN]; int tmp[MaxN];
void updateL(int l,int r){
	for(int i=l,v=0; i!=r; ++i){
		ans[i] = max(ans[i],v);
		v = max(v,tmp[i]); // Kai Qv Jian
	}
}
void updateR(int l,int r){
	for(int i=r-1,v=0; i>=l; --i){
		ans[i] = max(ans[i],v);
		v = max(v,tmp[i]); // Kai Qv Jian
	}
}
void solve(int l,int r){
	if(r-l == 1) return ;
	int mid = (l+r)>>1;
	solve(l,mid), solve(mid,r);
	/* a_l < a_r for r */ ;
	int_ v = -INF;
	for(int j=mid,i=l; j!=r; ++j){
		for(; i!=mid&&ren[i]<ren[j]; ++i)
			v = max(v,0ll+ren[i].pos+ren[i].val);
		tmp[ren[j].pos] = max(-1ll,v-ren[j].pos+1);
	}
	/* a_l >= a_r for r */ ;
	v = -INF;
	for(int j=r-1,i=mid-1; j>=mid; --j){
		for(; i>=l&&!(ren[i]<ren[j]); --i)
			v = max(v,0ll+ren[i].pos);
		tmp[ren[j].pos] = max(
			0ll+tmp[ren[j].pos],
			v+ren[j].val-ren[j].pos+1
		);
	}
	updateR(mid,r); // release tag
	/* a_l >= a_r for l */ ;
	v = -INF;
	for(int i=l,j=mid; i!=mid; ++i){
		for(; j!=r&&!(ren[i]<ren[j]); ++j)
			v = max(v,0ll+ren[j].val-ren[j].pos);
		tmp[ren[i].pos] = max(-1ll,v+ren[i].pos+1);
	}
	/* a_l < a_r for l */ ;
	v = -INF;
	for(int i=mid-1,j=r-1; i>=l; --i){
		for(; j>=mid&&ren[i]<ren[j]; --j)
			v = max(v,0ll-ren[j].pos);
		tmp[ren[i].pos] = max(
			0ll+tmp[ren[i].pos],
			v+ren[i].pos+ren[i].val+1
		);
	}
	updateL(l,mid);
	/* merge sort */ ;
	for(int i=l,j=mid; i!=mid||j!=r; )
		if(j == r || (i != mid && ren[i] < ren[j]))
			xyx[i+j-mid] = ren[i], ++ i;
		else xyx[i+j-mid] = ren[j], ++ j;
	for(int i=l; i!=r; ++i) ren[i] = xyx[i];
}

int ori[MaxN]; // original val
int main(){
	int n = readint();
	for(int i=1; i<=n; ++i){
		ren[i].pos = i;
		ren[i].val = readint();
		ori[i] = ren[i].val;
		ans[i] = -1; // init
	}
	ren[0].pos = 0; // virtual wall
	ren[0].val = INF>>1;
	solve(0,n+1); // right open
	for(int i=0; i<=n; ++i)
		xyx[ren[i].pos] = ren[i];
	for(int i=0; i<=n; ++i)
		ren[i] = xyx[i]; // restore
	ren[n+1].pos = n+1; // wall
	ren[n+1].val = INF>>1;
	solve(1,n+2); // right open
	int calc = 0;
	for(int i=1; i<=n; ++i)
		if(ans[i] < ori[i])
			++ calc;
	printf("%d\n",calc);
	for(int i=1; i<=n; ++i)
		if(ans[i] < ori[i])
			printf("%d ",i);
	putchar('\n');
	return 0;
}

吐槽

那种 O ( n 2 ) \mathcal O(n^2) O(n2) 的做法,可以优化吗?比如用 S T \tt ST ST + + + 倍增?

其实它只会让你白白地多一个 log ⁡ \log log 。有一种很厉害的数据,专门卡这玩意儿的:左边是 x , x − 2 , x − 4 , … , n 2 x,x-2,x-4,\dots,\frac{n}{2} x,x2,x4,,2n,中间是任意的 n 2 \frac{n}{2} 2n 0 / 1 0/1 0/1,右边是 n 2 + 1 , n 2 + 3 , n 2 + 5 , … , x + 1 \frac{n}{2}+1,\frac{n}{2}+3,\frac{n}{2}+5,\dots,x+1 2n+1,2n+3,2n+5,,x+1 。不难发现,对于这种 “山沟沟” 型数据,谷底的元素都不得不左右横跳,一定是 O ( n 2 ) \mathcal O(n^2) O(n2) 的。

可是造数据的人比较哈板善良,只用了 1 , 2 , 3 , … , n 1,2,3,\dots,n 1,2,3,,n 来卡。此时 S T \tt ST ST + + + 倍增就会是 O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n) 的。话说明明就是只准备让单 log ⁡ \sout{\log} log过的,俩 log ⁡ \sout{\log} log也卡不掉吗

我只觉得 命运对一个人的支配作用是绝对的。已经出现过两次了,明明写的算法很容易被卡掉,结果都 A C AC AC 了……显然都不是我……

正解

有一个更强大的转化:能干掉序列里能力值最强的就行。找到最大值,左侧与右侧都是递归子问题。不难发现这就是 笛卡尔树。复杂度 O ( n ) \mathcal O(n) O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值