• LOJ 2719 「NOI2018」冒泡排序——模型转化


    题目:https://loj.ac/problem/2719

    首先要发现合法的充要条件是 | LDS | <=2 !

    因为有没用的步数,说明一个元素先往左移、又往右移(不会先往右移再往左移,因为一旦往右移,说明它是前缀最大值,并且一直是),就说明它前面有一个比它大的、后面有一个比它小的,即有长度至少为 3 的 LDS 。

    考虑 DP ,已填了前 i 个位置。注意到已经填过的最大的数最容易产生 LDS ,所以令 dp[ i ][ j ] 表示填了前 i 个位置、已填的最大数是 j 的方案数。

    考虑第 i 个位置,如果填一个 < j 的数,只能填未填的最小数(这个发现很重要!),不然之后就会产生长度为 3 的 LDS 了;如果填一个 > j 的数,就没什么要注意的。

    所以有 dp[ i ][ j ]*1 -> dp[ i+1 ][ k ] ( k>=j )

    注意到系数全是 1 ,考虑用前缀和的思想来优化。就是 dp[ i ][ j ] 只向 dp[ i+1 ][ j ] 转移,然后 dp[ i ][ j ] 可以向 dp[ i ][ j+1 ] 转移。

    如果 j = i ,考虑到没有 dp[ i+1 ][ j ] ,所以 dp[ i ][ j ] 向 dp[ i+1 ][ j+1 ] 转移即可。

    每个状态画成二维格点,发现转移就是只能向右或向上、右下角一些位置不能走的 “从 ( 0 , 0 ) 走到 ( n , n ) ” 的方案数!可用卡特兰数。

    那个斜着的边,把它改成横平竖直的即可。那么就是不能经过 y=x-2 这条直线。从 ( x1,y1 ) 走到 ( x2,y2 ) 的方案数就是 ( C_{x2-x1+y2-y1}^{x2-x1} - C_{x2-(y1+1)+y2-(x1-1)}^{x2-(y1+1)} ) 。

    注意第0列是没有竖着的转移的。可以认为是从 ( 1 , 1 ) 走到 ( n , n ) 。

    然后要保证字典序 > q 。考虑数位 DP 。

    设当前做到第 i 位、之前都是贴着上界。设之前贴上界的已填数中最大值是 mx ,未填的最小值是 mn 。

    第 i 位不贴上界,设填 k ,需要 k > q[ i ] ;还需要 k > mx ,因为若 k < mx ,又有 k > q[ i ] ,所以 mx , k , q[ i ] 会组成长度为 3 的 LDS 。

    那么就是要 ( sumlimits_{k>max(mx,q[i])} dp[i][k] ) ;这是一个后缀和,正好就是从 ( i , max( mx,q[ i ] )+1 ) 走到 ( n , n ) 的不经过 y=x-2 的方案数。

    如果贴上界,需要满足 q[ i ] > mx || q[ i ] == mn ,不然会出现长度 >=3 的 LDS 。如果不满足的话,就没有从第 i 位往后贴上界的方案了,直接 break 掉。

    注意预处理至 2*n 。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    int Mx(int a,int b){return a>b?a:b;}
    const int N=12e5+5,mod=998244353;//N=6e5*2!!!
    int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}
    int pw(int x,int k)
    {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;}
    int n,a[N],jc[N],jcn[N]; bool vis[N];
    void init()
    {
      int lm=12e5;
      jc[0]=1;for(int i=1;i<=lm;i++)jc[i]=(ll)jc[i-1]*i%mod;
      jcn[lm]=pw(jc[lm],mod-2);
      for(int i=lm-1;i>=0;i--)jcn[i]=(ll)jcn[i+1]*(i+1)%mod;
    }
    int C(int n,int m)
    {
      if(n<0||m<0||n<m)return 0;
      return (ll)jc[n]*jcn[m]%mod*jcn[n-m]%mod;
    }
    int cal(int x,int y)
    { return upt(C(2*n-x-y,n-x)-C(2*n-x-y,n-y-2));}
    int main()
    {
      freopen("inverse.in","r",stdin);
      freopen("inverse.out","w",stdout);
      int T=rdn(); init();
      while(T--)
        {
          n=rdn(); for(int i=1;i<=n;i++)a[i]=rdn();
          memset(vis,0,sizeof vis); int mn=1,mx=0,ans=0;
          for(int i=1;i<=n;i++)
        {
          ans=upt(ans+cal(i,Mx(a[i],mx)+1));
          if(a[i]<mx&&a[i]!=mn)break;
          mx=Mx(mx,a[i]); vis[a[i]]=1;
          while(i<n&&vis[mn])mn++;
        }
          printf("%d
    ",ans);
        }
      return 0;
    }
    View Code
  • 相关阅读:
    在VSCode中使用码云
    自定义博客样式
    bolb、bloburl、file、base64间的转换
    html2canvas的使用:vue中将div导出成图片
    vue-to-pdf的使用:vue中将div转换为pdf文件
    早期javac编译器优化
    HttpClient4.3 连接池参数配置及源码解读
    Java编译程序和运行过程详解
    JVM中的常量池详解
    MySQL索引背后的数据结构及原理
  • 原文地址:https://www.cnblogs.com/Narh/p/10952099.html
Copyright © 2020-2023  润新知