【08.31】 Codeforces Round #740 (Div. 1) D题 Top-Notch Insertions (组合数学+平衡树/树状数组上二分)

传送门

题意

对一个序列做插入排序,插入排序规则如下:

  1. \(a_x\geq a_{x-1}\),则什么也不做
  2. 否则,在\(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}\),总方案数为

\[\sum_{i=0}^{n-1-c} \binom{n-1-c}{i}\binom{n}{c+i+1}=\sum_{i=0}^{n-1-c} \binom{n-1-c}{i}\binom{n}{n-c-i-1}=\binom{2n-1-c}{n-c-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;
}
posted @ 2021-08-31 12:00  Knowledge-Pig  阅读(27)  评论(0)    收藏  举报