• [NOI2018] 冒泡排序


    一、题目

    点此看题

    二、解法

    因为 \(\frac{1}{2}\sum_{i=1}^n|i-p_i|=\sum_{i=1}^n\max(i-p_i,0)\),我们可以考虑后者。

    \(d_i\) 表示位置 \(i\) 的逆序对数,显然有 \(d_i\geq \max(i-p_i,0)\),因为如果 \(p_i\leq i\),那么前面比 \(p_i\) 大的数至少有 \(i-p_i\) 个。

    这说明必须每个位置都要满足 \(d_i=\max(i-p_i,0)\) 才是好排列,这等价于对于所有位置 \(i\),要么 \(p_i\) 是前缀最大值,要么所有比 \(p_i\) 小的数都在 \([1,i)\) 中出现过了

    得到这个结论之后我们不考虑字典序限制,设 \(dp[i][j]\) 表示填了前 \(i\) 个位置最大值是 \(j\),那么转移就是填上一个更大的值,或者是填上前面所有数的 \(\tt mex\),所以转移的形式其实很简洁:

    \[dp[i][j]=\sum_{k=0}^{j} dp[i-1][k] \]

    此外还需要保证 \(i\leq j\),我们可以把它改写成前缀和的形式:

    \[sum[i][j]=sum[i-1][j]+sum[i][j-1] \]

    考虑转移的组合意义,这相当于在二维平面上行走,从 \((0,0)\) 开始,每次可以向上走或者向右走,要求不越过 \(y=x\),最后走到 \((n,n)\) 的方案数。显然这是卡特兰数,表达式是 \({2n\choose n}-{2n\choose n+1}\)


    显然考虑字典序限制,显然的方法是保证前缀相同,下一个位置 \(=p_i+1\),后面位置随意填的排列数。

    所以我们在保证前缀 \(i-1\) 合法的情况下,从 \((i-1,\max_{j=1}^ip_j+1)\) 开始行走到 \((n,n)\) 的方案数。我们已知从 \((x,y)\) 行走到 \((n,n)\) 的方案数是 \({2n-x-y\choose n-x}-{2n-x-y\choose n-x+1}\)

    #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(); 
    }
    
  • 相关阅读:
    面向对象
    tomcat启动时的java_home和jre_home错误
    C#获取当前程序运行路径的方法集合(转)
    Windows Media Player 打不开怎么办
    CGI/MIME/servlet术语解释
    HTTP 无法注册 URL http://+:8000/。进程不具有此命名空间的访问权限
    使用QuartZ.net来做定时计划任务 ; 值不能为 null。 参数名: assemblyString
    cz.msebera.android.httpclient.conn.ConnectTimeoutException: Connect to /192.168.23.1:8080 timed out(Android访问后台一直说链接超时)
    Java的位运算符——&0xFF的运算与讲解
    android+myeclipse+mysql自定义控件下拉框的数据绑定
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15960976.html
Copyright © 2020-2023  润新知