【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;
}
posted @ 2021-10-17 11:55  stoorz  阅读(33)  评论(0编辑  收藏  举报