\(\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;
}