【08.31】 Codeforces Round #740 (Div. 1) D题 Top-Notch Insertions (组合数学+平衡树/树状数组上二分)
题意
对一个序列做插入排序,插入排序规则如下:
- 若\(a_x\geq a_{x-1}\),则什么也不做
- 否则,在\(1\)到\(x-1\)的位置上找到一个最小的\(y\)使得\(a_y>a_x\),并将\(a_x\)插入在位置y上。记为\((x,y)\)。
给定序列长度n和插入次数m,再给定m对插入操作\((x_i,y_i)\),有多少种可能的值在\([1,n]\)的序列按照插入排序过程产生的插入操作序列为给定的操作序列。
题解
这题确实是一道好题。
记插入排序之后的序列为b,满足\(b_i\leq b_{i + 1}\)。给定的插入排序操作序列对于最终的序列b会产生什么影响呢?它会对序列b产生一些限制,也就是某些位置的\(b_i\)会严格大于前面的数。那么我们只要知道有多少合法的序列b,就可以知道对应有多少合法的原始序列a了。
假设当前序列b上计算得到在某c个位置要严格大于前面的数,其他位置上只要求大于等于。那么满足限制的序列b有\(\binom{2n-1-c}{n}\)个。
tourist的证明:
对于第i个数,我们使\(a_i\rightarrow a_i+i-1\),若存在位置x要求\(a_x>a_{x-1}\),则对于\(i\in [x, n]\),所有\(a_i\)全部减1,这样操作得到的新序列b'是严格递增的,且数的范围在\([1,2n-1-c]\),可以想到b与b'是一一映射关系。对于序列b'的个数为\(\binom{2n-1-c}{n}\)。
我的证明:
隔板法。当前有c个位置已经放置了隔板,剩下\(n-1-c\)个位置可以放隔板,放剩下位置放i个隔板,则序列被分成\(c+i+1\)块,每一块选用同一个数,则各块构成一个严格递增序列,我们从n个数里面选\(c+i+1\)个数的方案为\(\binom{n}{c+i+1}\),总方案数为
剩下的问题就是给定的操作序列会产生多少c。
考虑到\(x_i\)插入到\(y_i\)位置,根据以上分析,我们唯一关心的只是哪些位置要严格大于前面的数,所以对于序列b产生的唯一限制条件就是\(y_i+1\)位置是一个特殊位置。
为了维护这些特殊位置,我的做法是建了一棵平衡树无旋treap。
我的做法:
插入\(y_i\)的时候,查询\(y_i\)在不在平衡树上,如果在,则说明这次操作不能产生新的特殊位置,否则说明会新添加一个特殊位置\(y_i\),然后再将平衡树上\(\geq y_i\)的部分全部加1(因为插入排序,插入位置后的特殊位置会往后退一格),这里把树split后,再打一个懒标记即可,我也是这道题第一次在无旋treap上打懒标记,开始还有点犹豫担心会出问题,但最终AC了说明这是可行的。
大佬的做法:
我的做法代码还是比较复杂,于是查看了前面红名爷的简单写法。我们倒着考虑这m个插入限制,最开始序列是有序的,若有数插入\(y_i\)位置则说明\(y_{i+1}\)是一个特殊位置,之后我们把\(y_i\)删掉,也就是每次找集合中第\(y_i\)大与第\(y_i+1\)的数,将前者从集合中删去,将后者打上特殊位置标记。如何快速找到集合中第k大的数呢?可以采用在树状数组二分的方式进行查询,这根据树状数组的结构有关,不太好说,具体见代码。
代码·无旋treap
/*************************************************************************
> File Name: 1.cpp
> Author: Knowledge-Pig
> Mail: 925538513@qq.com
> Blog: https://www.cnblogs.com/Knowledge-Pig/
> Created Time: 2021年08月30日 星期一 08时25分56秒
************************************************************************/
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define LL long long
#define pb push_back
#define fi first
#define se second
#define pr pair<int,int>
#define mk(a,b) make_pair(a,b)
#define endl '\n'
#define lowbit(x) (x & (-x))
using namespace std;
const int N = 4e5, mod = 998244353;
int n, m, x[N], y[N];
LL fac[N + 10], inv[N + 10];
int qpow(LL x, int y){
LL res = 1;
while(y){
if(y&1) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
struct TREAP{
int key[N], son[N][2], rd[N], lazy[N], num, rt;
void clear(){
for(int i = 0; i <= num; ++i) key[i] = rd[i] = lazy[i] = son[i][0] = son[i][1] = 0;
num = rt = 0;
}
void push_down(int x){
if(!lazy[x]) return;
key[son[x][0]] += lazy[x];
key[son[x][1]] += lazy[x];
lazy[son[x][0]] += lazy[x];
lazy[son[x][1]] += lazy[x];
lazy[x] = 0;
}
int add(int val){
key[++num] = val;
rd[num] = rand();
return num;
}
void split(int p, int &l, int &r, int val){
if(!p){
l = r = 0;
return;
}
push_down(p);
if(key[p] <= val){
l = p;
split(son[l][1], son[l][1], r, val);
}
else{
r = p;
split(son[r][0], l, son[r][0], val);
}
}
void merge(int &p, int l, int r){
if(!l || !r){ p = l + r; return; }
if(rd[l] < rd[r]){
p = l;
push_down(p);
merge(son[p][1], son[p][1], r);
}
else{
p = r;
push_down(p);
merge(son[p][0], l, son[p][0]);
}
}
void solve(int x){
int l = 0, mid = 0, r = 0;
split(rt, l, r, x - 1);
split(r, mid, r, x);
// cout << l << " " << mid << " " << r << endl;
if(!mid) mid = add(x);
merge(r, mid, r);
++key[r];
++lazy[r];
// cout << key[r] << endl;
merge(rt, l, r);
}
}treap;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
freopen("output.out", "w", stdout);
#endif
int T;
fac[0] = 1;
for(int i = 1; i <= N; ++i) fac[i] = fac[i - 1] * i % mod;
inv[N] = qpow(fac[N], mod - 2);
for(int i = N - 1; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
cin >> T;
while(T--){
cin >> n >> m; treap.clear();
for(int i = 1; i <= m; ++i) cin >> x[i] >> y[i];
for(int i = 1; i <= m; ++i) treap.solve(y[i]);
int cnt = treap.num;
// cout << cnt << endl;
cout << fac[n * 2 - 1 - cnt] * inv[n] % mod * inv[n - 1 - cnt] % mod << endl;
}
return 0;
}
代码·树状数组二分
/*************************************************************************
> File Name: 1.cpp
> Author: Knowledge-Pig
> Mail: 925538513@qq.com
> Blog: https://www.cnblogs.com/Knowledge-Pig/
> Created Time: 2021年08月30日 星期一 08时25分56秒
************************************************************************/
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define LL long long
#define pb push_back
#define fi first
#define se second
#define pr pair<int,int>
#define mk(a,b) make_pair(a,b)
#define endl '\n'
#define lowbit(x) (x & (-x))
using namespace std;
const int N = 4e5, mod = 998244353;
int n, m, x[N], y[N], c[N + 10];
LL fac[N + 10], inv[N + 10];
bool vis[N];
int qpow(LL x, int y){
LL res = 1;
while(y){
if(y&1) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
int query(int k){
int pos = 0;
for(int i = 17, j; i >= 0; --i){
j = (1 << i);
if(pos + j < n && c[pos + j] < k){
pos += j;
k -= c[pos];
}
}
return pos + 1;
}
void add(int i, int val){
for(; i <= n; i += lowbit(i)) c[i] += val;
}
int main(){
ios::sync_with_stdio(false); cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
freopen("output.out", "w", stdout);
#endif
int T, cnt = 0;
fac[0] = 1;
for(int i = 1; i <= N; ++i){
fac[i] = fac[i - 1] * i % mod;
c[i] = lowbit(i);
}
inv[N] = qpow(fac[N], mod - 2);
for(int i = N - 1; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % mod;
cin >> T;
while(T--){
cin >> n >> m; cnt = 0;
for(int i = 1; i <= m; ++i) cin >> x[i] >> y[i];
for(int i = m; i >= 1; --i){
int p = query(y[i]), q = (y[i] < n - m + i) ? query(y[i] + 1) : 0;
add(p, -1);
if(q && !vis[q]) ++cnt, vis[q] = 1;
x[i] = p; y[i] = q;
}
for(int i = 1; i <= m; ++i) add(x[i], 1), vis[y[i]] = 0;
cout << fac[n * 2 - 1 - cnt] * inv[n] % mod * inv[n - 1 - cnt] % mod << endl;
}
return 0;
}

浙公网安备 33010602011771号