\(\text{Bottom-Tier Reversals}\)

解法

首先可以观察得到 reverse 操作并不会改变数字所在下标的奇偶性,这可以判掉无解。

如果我们将 \(n-1,n\) 排到正确的位置就不会再改变它的位置,这相当于将原问题缩减到 \(n-2\)。所以我们可以先考虑如何排这两个数。需要注意的是,\(n-1\) 在偶数位置,\(n\) 在奇数位置。

不妨倒着构造。首先需要达到 "\(n\)\(1\) 位置,\(n-1\)\(2\) 位置" 的状态,由此可知肯定还存在一个 "\(n\)\(i+1\) 位置,\(n-1\)\(i\) 位置" 的状态,而这个状态可以由 "\(n\)\(j-1\) 位置,\(n-1\)\(j\) 位置" 得到,只要我们翻一个 \(>j\) 的位置就行了。于是不妨将 \(n\) 先翻到 \(1\),设此时 \(n-1\) 的位置为 \(p\),将 \(n\) 翻到 \(p-1\),就可以倒着构造一组解。一共需要 \(5\) 次。

所以总次数是 \(\frac{5(n-1)}{2}\)

\(\text{Top-Notch Insertions}\)

题目大意

对长度为 \(n\) 的序列 \(a\) 进行插入排序,其中每个元素都在区间 \([1,n]\) 之间,元素可以相等。从小到大枚举每个位置 \(i\),如果有 \(a_{i-1}>a_i\),就找到 第一个 位置 \(j\) 满足 \(a_i<a_j\) 并把 \(i\) 插入到 \(j\) 的前面,并把其记为操作 \((i,j)\)

给出长度为 \(m\) 的操作序列 \(p\),请算出有多少个序列 \(a\) 的插入排序的操作序列为 \(p\)

多组数据,\(n,\sum m\le 2\cdot 10^5\)

解法

对于固定的操作序列,原序列和终序列是一一对应的。问题转化为统计合法终序列的个数。

不妨设终序列为 \(b\)。那么它肯定满足 \(\forall i\in[1,n),b_i\le b_{i+1}\)

但事实上操作序列要求某些关系是 < 的。考虑对于固定的操作序列,终序列中每个符号的位置都是固定的!所以统计个数就转化成了一个组合问题。终序列中有 \(n-1\) 个符号,设有 \(c\)<,那么方案数可以表示成 \(\binom{2n-c-1}{n}\)。具体证明就是对于每个 \(b_i\le b_{i+1}\),将 \([i+1,n]\) 中的 \(b\) 全部加一。这样的好处就是所有数都互不相同,而且相对关系不会发生变化。由于初始每个元素都在区间 \([1,n]\) 之间,所以此时元素的最大值就是 \(n+(n-1-c)\)。因为排列顺序已经确定,所以只用从 \([1,2n-1-c]\) 中选择 \(n\) 个数即可。倒推回原来的序列可以用相邻两项相差 \(1\)

如何求解 \(c\)?我们维护一个 集合 存储有哪些数 "被插" 了,答案就是集合大小(一个数可能多次 "被插",这也是 \(m\) 可能不等于 \(c\) 的原因)。可以写一个平衡树,但实际上可以倒着维护,写一个线段树二分。具体而言,对于每个操作 \((x,y)\),找到当前第 \(y\) 个数与第 \(y+1\) 个数,第 \(y\) 个数就是这个操作插入的数,直接在线段树上删掉它。因为操作序列的 \(x\) 是递增的,所以不将第 \(y\) 个数插回去也不会影响之前的插入。

不过需要注意复杂度要写成 \(\mathcal O(m\log n)\)

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <set>
using namespace std;

const int maxn=2e5+5,mod=998244353;

int n,m,x[maxn],y[maxn],roll[maxn];
int fac[maxn<<1],ifac[maxn<<1];
set <int> ans;
struct SgTree {
	int t[maxn<<2];
	
	void build(int o,int l,int r) {
		t[o]=r-l+1;
		if(l==r) return;
		int mid=l+r>>1;
		build(o<<1,l,mid);
		build(o<<1|1,mid+1,r);
	}
	
	int ask(int o,int l,int r,int k) {
		while(233) {
			int mid=l+r>>1;
			if(l==r) return l;
			if(k<=t[o<<1])
				o<<=1,r=mid;
			else {
				k-=t[o<<1];
				o=o<<1|1; l=mid+1;
			}
		} 
	}
	
	void modify(int o,int l,int r,int p,int k) {
		if(l==r) return (void)(t[o]+=k);
		int mid=l+r>>1;
		if(p<=mid) modify(o<<1,l,mid,p,k);
		else modify(o<<1|1,mid+1,r,p,k);
		t[o]=t[o<<1]+t[o<<1|1];
	}
} T;

int inv(int x,int y=mod-2) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod; 
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

void init() {
	fac[0]=1;
	for(int i=1;i<=(int)4e5;++i)
		fac[i]=1ll*fac[i-1]*i%mod;
	ifac[(int)4e5]=inv(fac[(int)4e5]);
	for(int i=(int)4e5-1;i>=0;--i)
		ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
	T.build(1,1,maxn-5);
}

int C(int n,int m) {
	if(n<m or n<0 or m<0) return 0;
	return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

int main() {
	init();
	for(int t=read(9);t;--t) {
		n=read(9),m=read(9);
		ans.clear();
		for(int i=1;i<=m;++i)
			x[i]=read(9),y[i]=read(9);
		for(int i=m;i>=1;--i) {
			int u=T.ask(1,1,maxn-5,y[i]);
			int v=T.ask(1,1,maxn-5,y[i]+1);
			T.modify(1,1,maxn-5,u,-1);
			ans.insert(v);
			roll[i]=u;
		}
		for(int i=1;i<=m;++i)
			T.modify(1,1,maxn-5,roll[i],1);
		int p=ans.size();
		print(C(2*n-p-1,n),'\n');
	}
	return 0;
}
posted on 2021-08-25 22:38  Oxide  阅读(60)  评论(0编辑  收藏  举报