题目
题目描述
有一场不公平的竞赛,一共有
n
n
n 位参赛选手,站成一列,第
i
i
i 位参赛选手的初始实力值为
a
i
a_i
ai 。
一共有 n − 1 n−1 n−1 场比赛,每场比赛你可以任意选择两个相邻的参赛选手,实力值大的胜出(若两个人实力相同则你可以指定胜者)。这看上去很公平。糟糕的是,由于战斗经验的增加,胜者的实力值会永久加一。败者离开这场比赛(从这个选手序列中删除)。在这 n − 1 n−1 n−1 场比赛后剩下的那一名选手即为冠军。
容易看出冠军不一定是初始实力值最大的选手。求哪些选手可能是最后的冠军。
数据范围与提示
n
≤
5
×
1
0
5
,
0
≤
a
i
<
2
31
n\le 5\times 10^5,\;0\le a_i<2^{31}
n≤5×105,0≤ai<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) r−l−2+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) r−l−1+ax≤min(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 ax≤al+l−r+1,最小化 r r r 并且最大化 a l + l a_l+l al+l 即可。
如果 a l ≥ a r a_l\ge a_r al≥ar,只需要 a x ≤ a r − r + l + 1 a_x\le a_r-r+l+1 ax≤ar−r+l+1,最大化 l l l 并且最大化 a r − r a_r-r ar−r 即可。
能不能 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,x−2,x−4,…,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) 。