CF1685E The Ultimate LIS Problem 【贪心,AGC】
给定长为 \(2n+1\) 的排列 \(p_1,\cdots,p_{2n+1}\),\(q\) 次操作,每次给出 \(u,v\),表示 \(\text{swap}(p_u,p_v)\) 然后求任意 \(k\in[0,2n]\) 使得 \(p_{k+1},\cdots,p_{2n+1},p_1,\cdots,p_k\) 的 LIS 长度 \(\le n\),需判断无解。
\(n,q\le 10^5\)。
怎么判无解阿?就是说所有循环移位的 LIS 长度 \(\ge n+1\)。
一个括号序列的结论突然出现:令 \(b_i=[p_i>n+1]-[p_i<n+1]\),其包含 \(n\) 个 \(1\)、\(n\) 个 \(-1\) 和 \(1\) 个 \(0\),存在一个循环移位满足所有前缀和 \(\ge 0\)。看看这个循环移位的 LIS,如果不包含 \(n+1\) 则是 \(x\) 个 \(-1\) 接上 \(n+1-x\) 个 \(1\),从而第 \(x\) 个 \(-1\) 在第 \(x\) 个 \(1\) 之前,矛盾了,所以 LIS 必定包含 \(n+1\),之前是 \(x\) 个 \(-1\),之后是 \(n-x\) 个 \(1\),又由前缀和 \(\ge 0\) 的结论知 \(n+1\) 之前也有 \(x\) 个 \(1\),之后也有 \(n-x\) 个 \(-1\)。
这个结论启发我们看看 \(n+1\) 在开头和结尾的循环移位的 LIS,可以发现前缀和仍然 \(\ge 0\),所以 LIS 必定包含 \(n+1\),从而两个 LIS 分别为 \(n+1,\cdots,2n+1\) 和 \(1,\cdots,n+1\),也就是说 \(n+1\) 在开头时 \(i\) 和 \(n+1+i\) 都按顺序出现,且 \(n+1+i\) 在 \(i\) 之前(\(i\in[1,n]\))。
事实证明满足这个条件就无解了:对于位置 \(k+1\) 开头的循环移位,位置 \(k\) 之后 \(<n+1\) 的数然后 \(n+1\) 接着位置 \(k\) 之前 \(>n+1\) 的数就是长度 \(\ge n+1\) 的上升序列。
这也推出了求解方法:看看 \(n+1\) 在开头的循环移位,
- 若有前缀和 \(<0\),就输出任意一个所有前缀和 \(\ge 0\) 的循环移位;
- 若 \(i\) 不按顺序出现,就输出 \(n+1\) 结尾的循环移位;
- 若 \(n+1+i\) 不按顺序出现,就输出 \(n+1\) 开头的循环移位。
维护逆排列 \(\text{pos}_i\),然后维护 \(\sum_{i=1}^n[\text{pos}_{i+1}<\text{pos}_i]+[\text{pos}_1<\text{pos}_{n+1}]\) 和 \(\sum_{i=1}^n[\text{pos}_{n+i+1}<\text{pos}_{n+i}]+[\text{pos}_{n+1}<\text{pos}_{2n+1}]\),再用线段树维护前缀和最小值,直接模拟即可,时间复杂度 \(\mathcal O((n+q)\log n)\)。
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 200003, M = 1 << 19;
int n, q, m, p[N], ps[N], seg[M], sm1, sm2;
pii mn[M];
int sgn(int x){return (x > n + 1) - (x <= n);}
void pup(int x){
seg[x] = seg[x << 1] + seg[x << 1 | 1];
mn[x] = min(mn[x << 1], MP(mn[x << 1 | 1].fi + seg[x << 1], mn[x << 1 | 1].se));
}
void build(int x = 1, int L = 1, int R = m){
if(L == R){mn[x] = MP(seg[x] = sgn(p[L]), L); return;}
int md = L + R >> 1;
build(x << 1, L, md);
build(x << 1 | 1, md + 1, R);
pup(x);
}
void upd(int pos, int x = 1, int L = 1, int R = m){
if(L == R){mn[x] = MP(seg[x] = sgn(p[L]), L); return;}
int md = L + R >> 1;
if(pos <= md) upd(pos, x << 1, L, md);
else upd(pos, x << 1 | 1, md + 1, R);
pup(x);
}
int qry(int pos, int x = 1, int L = 1, int R = m){
if(L == R) return 0;
int md = L + R >> 1;
if(pos <= md) return qry(pos, x << 1, L, md);
return seg[x << 1] + qry(pos, x << 1 | 1, md + 1, R);
}
void work(int x, int val){
if(x <= n + 1){
int lst = x == 1 ? n + 1 : x - 1, nxt = x == n + 1 ? 1 : x + 1;
sm1 -= (ps[x] < ps[lst]) + (ps[nxt] < ps[x]);
}
if(x >= n + 1){
int lst = x == n + 1 ? m : x - 1, nxt = x == m ? n + 1 : x + 1;
sm2 -= (ps[x] < ps[lst]) + (ps[nxt] < ps[x]);
}
ps[x] = val;
if(x <= n + 1){
int lst = x == 1 ? n + 1 : x - 1, nxt = x == n + 1 ? 1 : x + 1;
sm1 += (ps[x] < ps[lst]) + (ps[nxt] < ps[x]);
}
if(x >= n + 1){
int lst = x == n + 1 ? m : x - 1, nxt = x == m ? n + 1 : x + 1;
sm2 += (ps[x] < ps[lst]) + (ps[nxt] < ps[x]);
}
}
int main(){
ios::sync_with_stdio(0);
cin >> n >> q; m = n * 2 + 1;
for(int i = 1;i <= m;++ i){cin >> p[i]; ps[p[i]] = i;}
for(int i = 1;i <= n;++ i){
sm1 += ps[i + 1] < ps[i];
sm2 += ps[n + i + 1] < ps[n + i];
}
sm1 += ps[1] < ps[n + 1];
sm2 += ps[n + 1] < ps[m];
build();
while(q --){
int x, y; cin >> x >> y;
work(p[x], y); work(p[y], x);
swap(p[x], p[y]); upd(x); upd(y);
printf("%d\n", mn[1].fi < qry(ps[n + 1]) ? mn[1].se % m : (sm1 != 1 ? ps[n + 1] % m : (sm2 != 1 ? ps[n + 1] - 1 : -1)));
}
}