2 条题解

  • 0
    @ 2026-7-4 11:59:00

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 2000005;
    #define int long long
    const int MOD = 998244353;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,m,ans,fac[M],inv[M],b[M];
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m || m<0) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    int walk(int x,int y,int n,int m)
    {
    	if(x>n || y>m || x<0 || y<0) return 0;
    	return C(n+n-x-y,n-x)-C(n+n-x-y,n-x+1);
    }
    void add(int x)
    {
    	for(int i=x;i<=n;i+=(i&-i)) b[i]++;
    }
    int ask(int x)
    {
    	int r=0;
    	for(int i=x;i>0;i-=(i&-i)) r+=b[i];
    	return r;
    }
    void work()
    {
    	n=read();ans=0;
    	for(int i=1;i<=n;i++) b[i]=0;
    	for(int i=1,mx=0,fl=1;i<=n;i++)
    	{
    		int x=read();mx=max(mx,x);
    		if(fl) ans=(ans+walk(i-1,mx+1,n,n))%MOD;
    		add(x);
    		if(x!=mx && ask(x)<x) fl=0;
    	}
    	printf("%lld\n",(ans+MOD)%MOD);
    }
    signed main()
    {
    	T=read();init(2e6);
    	while(T--) work(); 
    }
    
    
    
    • 0
      @ 2026-5-14 17:11:44

      感谢给我讲了这个题的神仙跳瓜 jumpmelon\textrm{jumpmelon}

      首先看给的提示,我们可以发现在这样的排序方式下,对于每一个数都只向目标位置方向走,不换向。那么对于一个数 xx,如果有一个比它大的数在它前面,那么它必须向左走;如果有一个比它小的数在它后面,那么它必须向右走。这样的排列是不合法的,即要求不存在长度超过 22 的下降子序列。

      根据 Dilworth\textrm{Dilworth} 定理,最长下降子序列的长度不超过 22,即整个排列最多被划分成 22 个上升子序列。

      先不考虑字典序严格大于 qq 的限制。

      我们记 fi,jf_{i, j} 为前 ii 个的最大值为 jj 后面 nin - i 个位置的方案数。我们考虑第 ii 个数填什么。注意 iji \leqslant j

      如果填比 jj 大的数,那么一定可以接在 jj 的后面;如果要填比 jj 小的数,那么必须填当前还没有填的中最小的,否则上升子序列将不止 22 个,所以

      $$\begin{aligned} f_{i, j} &= \begin{cases} f_{i + 1, k} \ \ \ \ \ (k > j) \\ f_{i + 1, j} \ \ \ \ \ \end{cases} \\ &= \sum_{k = j}^n f_{i + 1, k} \end{aligned}$$

      但是直接这样递推是 O(n2)O(n^2) 的。我们把它以图像的形式表示,fi,jf_{i, j} 即表示从点 (i,j)(i, j) 开始,每次向右走一步,向上走 x(x0)x(x \leqslant 0) 步,不与直线 y=x1y = x - 1 (因为 iji \leqslant j) 相交,走到点 (n,n)(n, n) 的方案数。

      如图,即从点 A(i,j)A(i, j) 走到 B(n,n)B(n, n) 的不与直线 y=x1y = x - 1 相交的方案数。

      首先,如果不考虑与直线不相交,即为走 nin - i 次,每次选择向上走 x(x0)x (x \geqslant 0) 步,一共走了 njn - j 步的方案数。模仿插板法,因为 xx 可以取 00,我们把总个数加上划分数 nin - i,变成 ni+njn - i + n - j 个物品划分成 nin - i 块的方案数,即 (2nij1ni1)\dbinom{2n - i - j - 1}{n-i-1}

      再模仿 Catalan\textrm{Catalan} 数的推法,看第一个与直线 y=x1y = x - 1 相交的位置。找点 (i,j)(i, j) 关于直线 y=x1y = x - 1 的对称点 (j+1,i1)(j + 1, i - 1), 由于方案一一对应,所以,从点 (i,j)(i, j) 出发,经过直线的方案数即为从点 (j+1,i1)(j + 1, i - 1) 出发到点 (n,n)(n, n) 的方案数。

      得到

      $$\begin{aligned} f_{i, j} &= calc(i, j) - calc(j + 1, i - 1) \\ &= \dbinom{2n - i - j - 1}{n - i - 1} - \dbinom{2n - i - j - 1}{n - j - 2} \\ \end{aligned}$$

      这样就得到 ffO(1)O(1) 求解啦!(然而 O(n2)O(n^2) 有足足 8080 分,真香)

      可以发现 f0,0f_{0, 0} 即为 Catalan\textrm{Catalan} 数,可以得到 1212 分。

      回到有限制字典序严格大于 qq 的原题上来。考虑一位一位枚举,假设当前枚举到第 ii 项,我们计数证前 i1i - 1 项与 qq 相同,第 ii 项大于 qiq_i 的排列个数。

      mx=maxj=1i1qjmx = \max_{j = 1}^{i - 1} q_jmnmn 为当前还没有用过的最小的数,v=qiv = q_i。第 ii 位只能填 mnmn 或大于 mxmx 的数。分类讨论

      • v=mnv = mn

        (因为 mnmn 是最小可以填的,所以 vv 的下界是 mnmn。)

        此时,第 ii 项不能填 mnmn,只能大于 mxmx,故后面 nin - i 项的填法有 k=mx+1nfi,k\sum_{k = mx + 1}^n f_{i, k} 种(kk 为第 ii 位填的数)。

      • mn<v<mxmn < v < mx

        此时第 ii 位没有可以填的,后面不再存在合法方案。(但是还是要读完)

      • vmxv \geqslant mx

        此时第 ii 位可以填 mnmn 或大于 mxmx 的数,方案数为 k=mxnfi,k\sum_{k = mx}^n f_{i, k}

      问题又来了,怎么求 ff 的前缀和呢?考虑 ff 的递推式 fi,j=k=jnfi+1,kf_{i, j} = \sum_{k = j}^n f_{i + 1, k},这正是一个前缀和的形式。所以

      k=limnfi,k=fi1,lim\sum_{k = lim}^n f_{i, k} = f_{i - 1, lim}

      不用像其他题解上说的要用树状数组,复杂度 O(Tn)O(Tn)(还好写)

      完结撒花~

      代码

      注意数组要开 2n2n,写起来很简单,但是最开始由于没彻底搞懂想了半天

      #include <bits/stdc++.h>
      using namespace std;
      
      namespace TYC
      {
          typedef long long ll;
          const int N = 1.2e6, p = 998244353;
      
          int fac[N + 5], inv[N + 5], vis[N + 5];
      
          inline int read()
          {
              int v = 0, fl = 0, ch = getchar();
              while (!isdigit(ch))
                  fl |= (ch == '-'), ch = getchar();
              while (isdigit(ch))
                  v = v * 10 + ch - '0', ch = getchar();
              return fl ? -v : v;
          }
      
          inline int qpow(int v, int tim)
          {
              int ans = 1;
              for (; tim; tim >>= 1, v = (ll)v * v % p)
                  if (tim & 1)
                      ans = (ll)ans * v % p;
              return ans;
          }
      
          void init()
          {
              fac[0] = 1;
              for (int i = 1; i <= N; i++)
                  fac[i] = (ll)fac[i - 1] * i % p;
              inv[N] = qpow(fac[N], p - 2);
              for (int i = N; i; i--)
                  inv[i - 1] = (ll)inv[i] * i % p;
          }
      
          inline int C(const int n, const int m)
          {
              return (n < 0 || m < 0 || n < m) ? 0 : int((ll)fac[n] * inv[m] % p * inv[n - m] % p);
          }
      
          int n;
          inline int F(const int i, const int j)
          {
              return i <= j && j <= n ? (C(2 * n - i - j - 1, n - i - 1) - C(2 * n - i - j - 1, n - j - 2) + p) % p : 0;
          }
      
          void work()
          {
              init();
              int T = read();
              while (T--)
              {
                  n = read();
                  memset(vis, 0, sizeof(int[n + 1]));
                  int ans = 0, mx = 0, mn = 1, flag = 0, v;
                  for (int i = 1; i <= n; i++)
                  {
                      v = read();
                      if (flag)
                          continue;
                      ans = (ans + (F(i - 1, max(mx, v) + 1) + p) % p) % p;
                      if (mx > v && v > mn)
                          flag = 1;
                      mx = max(mx, v);
                      vis[v] = 1;
                      while (vis[mn]) mn++;
                  }
                  printf("%d\n", ans);
              }
          }
      }
      
      int main()
      {
          TYC::work();
          return 0;
      }
      

      P.S.
      upd 2023.8.5 我已经退役很多年了,一次碰巧上洛谷看到有同学提醒组合数写反了,现特为纠正,希望同学们都能看明白。

      • 1

      信息

      ID
      2568
      时间
      1000ms
      内存
      512MiB
      难度
      10
      标签
      递交数
      7
      已通过
      3
      上传者