【CF1558D】Top-Notch Insertions
题目
题目链接:https://codeforces.com/problemset/problem/1558/D
对于一个长度为 \(n\) 的序列 \(a\) 做插入排序,即依次考虑 \(a_2, \cdots, a_n\) 每个元素:
- 如果 \(a_i \ge a_{i - 1}\),即前缀依然保持不下降,不做操作。
- 否则,找到最靠前的位置 \(p\),使得 \(a_i< a_p\),然后将 \(a_i\) 插入到 \(a_p\) 前面,并重新标号序列。记这次插入为 \((i, p)\)。
接下来给定 \(m\) 个二元组 \((x_i, y_i)\),求有多少个长度为 \(n\) 的序列 \(a\),满足 \(\forall i, a_i \in [1, n] \cap \mathbb N\) 且做插入排序恰好进行给定的 \(m\) 次插入。
输出序列的数量对 \(998244353\) 取模的结果。多测。
\(Q\leq 10^5\),\(n,\sum m\leq 2\times 10^5\)。
思路
手玩一下发现,最后肯定是一个长度为 \(n\) 的序列,相邻两项之间有一个 \(\leq\) 或 \(<\) 的限制,求值域在 \([1,n]\) 范围内的序列数量。
这个东西只需要求出有多少个 \(<\),然后组合数随便计算一下就好了。
时间倒流,设处理到操作 \((x_i,y_i)\),那么现在的 \(n\) 个空位中就有若干个已经填上了 \((x_i,n]\) 之间的数。那么这一个操作说明 \(x_i\) 一定是填在目前的第 \(y_i\) 个空位中,且严格小于第 \(y_i+1\) 个空位上的数。
那么这一次操作会把 \(<\) 的数量加一,当且仅当第 \(y_i\) 个空位与第 \(y_i+1\) 个空位是相邻的。
所以可以用线段树维护区间空位数量,每次操作在线段树上二分即可。
但是这个题还有一个比较无聊的地方,就是没有保证 \(\sum n\) 的范围。所以每一组询问都重建线段树是不可行的。只需要每组询问搞定之后把操作撤回就行了。
时间复杂度 \(O(m\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,MOD=998244353;
int Q,n,m,cnt,a[N];
ll fac[N*2];
queue<int> q;
ll fpow(ll x,ll k)
{
ll ans=1;
for (;k;k>>=1,x=x*x%MOD)
if (k&1) ans=ans*x%MOD;
return ans;
}
void init()
{
fac[0]=1;
for (int i=1;i<2*N;i++)
fac[i]=fac[i-1]*i%MOD;
}
ll C(int n,int m)
{
if (n<m) return 0;
return fac[n]*fpow(fac[m],MOD-2)%MOD*fpow(fac[n-m],MOD-2)%MOD;
}
struct SegTree
{
int cnt[N*4];
void build(int x,int l,int r)
{
cnt[x]=r-l+1;
if (l==r) return;
int mid=(l+r)>>1;
build(x*2,l,mid); build(x*2+1,mid+1,r);
}
void update(int x,int l,int r,int k,int v)
{
cnt[x]+=v;
if (l==r) return;
int mid=(l+r)>>1;
if (k<=mid) update(x*2,l,mid,k,v);
if (k>mid) update(x*2+1,mid+1,r,k,v);
}
int query(int x,int l,int r,int k)
{
if (l==r) return l;
int mid=(l+r)>>1;
if (cnt[x*2]>=k) return query(x*2,l,mid,k);
else return query(x*2+1,mid+1,r,k-cnt[x*2]);
}
}seg;
int main()
{
init();
seg.build(1,1,N-1);
scanf("%d",&Q);
while (Q--)
{
scanf("%d%d",&n,&m);
for (int i=1,x;i<=m;i++)
scanf("%d%d",&x,&a[i]);
cnt=0;
for (int i=m;i>=1;i--)
{
int p1=seg.query(1,1,N-1,a[i]+1),p2=seg.query(1,1,N-1,a[i]);
if (p1-p2==1) cnt++;
seg.update(1,1,N-1,p2,-1);
q.push(p2);
}
cout<<C(n*2-cnt-1,n)<<"\n";
for (;q.size();q.pop())
seg.update(1,1,N-1,q.front(),1);
}
return 0;
}