zoukankan      html  css  js  c++  java
  • Codeforces 1542E2

    upd on 2021.7.7:修了个 typo

    Codeforces 题目传送门 & 洛谷题目传送门

    首先考虑怎样处理”字典序小“这个问题,按照字典序比大小的套路,我们可以枚举第一位 (p_x e q_x) 的位置 (x),那么必然有 (p_x<q_x),为了避免后面计算中出现太多形如 (n-x) 之类的东西,我们不枚举 (x),instead 我们枚举 (i=n-x),那么真正的 (x) 等于 (n) 减去你枚举的 (i)

    接下来考虑 (p) 逆序对个数大于 (q) 逆序对个数这个东西怎么处理,显然 (p,q) 的前 (n-i-1) 位是相同的,它们内部的逆序对个数是相同的,而由于 (p,q)(n-i) 位到第 (n) 位中数组成相同,(p,q)(n-i-1) 位与后 (i+1) 位之间的逆序对个数也相同,因此 (p) 逆序对个数大于 (q) 逆序对个数即意味着 (p)(i+1) 位逆序对个数 (>) (q)(i+1) 位逆序对个数。

    我们假设 (p)(i) 位逆序对个数为 (u)(q)(i) 位逆序对个数为 (v),那么不难发现,加入 (p_{n-i}) 后逆序对个数的变化只与 (p_{n-i}) 在后 (i+1) 个数中排第几有关——我们假设 (p_{n-i})(p)(i+1) 位中的第 (s) 小,(q_{n-i})(q)(n-i+1) 位中的第 (t) 小,那么后 (i+1) 位中 (p) 的逆序对数即为 (u+s-1)(q) 的逆序对数即为 (v+t-1)

    我们考虑枚举 (u,v),那么根据 (u+s-1>v+t-1) 可知 (t-s<u-v),又显然 (t>s),故 (0<t-s<u-v),根据 (t,sin[1,i+1]) 可知,满足 (t-s=x(xin[1,i]))(t,s) 共有 (i+1-x) 组,把它们累加起来可得 (sumlimits_{x=1}^{u-v-1}i+1-x=dfrac{i(i+1)}{2}-dfrac{(i+1-(u-v))(i+2-(u-v))}{2}),我们记 (f(i,x)=dfrac{i(i+1)}{2}-max(dfrac{(i+1-x)(i+2-x)}{2},0)),那么这东西就是 (f(i,u-v))

    接下来考虑怎样计算答案,首先我们设 (dp_{i,j}) 表示有多少个长度为 (i) 的排列有 (j) 个逆序对,那么显然有转移方程式 (dp_{i,j}=sumlimits_{k=0}^{i-1}dp_{i-1,j-k}),前缀和优化即可实现 (mathcal O(1)) 转移。那么我们枚举 (i,u,v),那么根据之前的推论填好 (p,q)(i+1) 位的相对大小关系的方案数为 (sumlimits_{u=0}^{i(i-1)/2}sumlimits_{v=0}^{u-1}dp_{i,u}dp_{i,v}f(i,u-v)),再乘上确定 (p,q)(n-i-1) 位的方案数——选出 (n-i-1) 个数的方案数为 (dbinom{n}{n-i-1}),将它们排列好的方案数为 ((n-i-1)!),因此总方案数就是 (sumlimits_{i=0}^{n-1}dbinom{n}{n-i-1}(n-i-1)!sumlimits_{u=0}^{i(i-1)/2}sumlimits_{v=0}^{u-1}dp_{i,u}dp_{i,v}f(i,u-v)),暴力计算是 (mathcal O(n^5)),无法通过 E2,可以通过 E1。如果稍微观察一下可知若 (v<u-i)(f(i,u-v)=dfrac{i(i+1)}{2}) 为定值,可以直接一波前缀和带走,时间复杂度可以降到 (n^4),但是还是无法通过 E2。

    附:E1 (mathcal O(n^4)) 的做法,注意,由于模数不是质数,不能预处理阶乘及其逆元来求组合数,需手动递推。

    const int MAXN=50;
    const int MAXM=1225;
    int n,mod,ans=0,dp[MAXN+5][MAXM+5],sum[MAXN+5][MAXM+5];
    int c[MAXN+5][MAXN+5],fac[MAXN+5];
    int main(){
    	scanf("%d%d",&n,&mod);dp[1][0]=1;
    	for(int i=0;i<=MAXM;i++) sum[1][i]=1;
    	for(int i=2;i<=n;i++){
    		for(int j=0;j<=i*(i-1)/2;j++){
    			dp[i][j]=sum[i-1][j];
    			if(j-i>=0) dp[i][j]=(dp[i][j]-sum[i-1][j-i]+mod)%mod;
    //			printf("%d %d %d
    ",i,j,dp[i][j]);
    		} sum[i][0]=dp[i][0];
    		for(int j=1;j<=MAXM;j++) sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
    	}
    	for(int i=(fac[0]=1)-1;i<=MAXN;i++){
    		c[i][0]=1;if(i) fac[i]=1ll*fac[i-1]*i%mod;
    		for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    	}
    	for(int i=0;i<n;i++){
    		int lim=i*(i+1)/2,s=0;
    		for(int j=0;j<=i*(i-1)/2;j++){
    			s=(s+1ll*lim*sum[i][j-1]%mod*dp[i][j])%mod;
    			for(int k=max(0,j-i);k<j;k++){
    				s=(s-1ll*(i-j+k+1)*(i-j+k+2)/2*dp[i][k]%mod*dp[i][j]%mod+mod)%mod;
    			}
    		} //printf("%d
    ",s);
    		ans=(ans+1ll*s*c[n][n-i-1]%mod*fac[n-i-1])%mod;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    接下来考虑优化这个做法,考虑后面这个式子 (sumlimits_{u=0}^{i(i-1)/2}sumlimits_{v=0}^{u-1}dp_{i,u}dp_{i,v}f(i,u-v)),我们不妨固定住 (u),考虑后面的东西怎么快速计算,我们不妨将 (f) 展开,可以得到:

    [egin{aligned} &sumlimits_{u=0}^{i(i-1)/2}sumlimits_{v=0}^{u-1}dp_{i,u}dp_{i,v}f(i,u-v)\ =&sumlimits_{u=0}^{i(i-1)/2}dp_{i,u}·sumlimits_{v=0}^{u-1}dp_{i,v}(dfrac{i(i+1)}{2}-max(dfrac{(i+1-(u-v))(i+2-(u-v))}{2},0))\ =&sumlimits_{u=0}^{i(i-1)/2}dp_{i,u}·sumlimits_{v=0}^{u-1}dp_{i,v}dfrac{i(i+1)}{2}-dp_{i,u}·sumlimits_{v=0}^{u-1}dp_{i,v}max(dfrac{(i+1-(u-v))(i+2-(u-v))}{2},0) end{aligned} ]

    前面的东西显然一波前缀和带走,对于后面的部分,如果 (v<u-i) 那显然是 (0),否则我们可以将 (max) 展开,继续化为 (sumlimits_{v=0}^{u-1}dp_{i,v}·dfrac{(i+1-u+v)(i+2-u+v)}{2}),记 (T=i+1+v),那么原式 (=sumlimits_{v=0}^{u-1}dp_{i,v}·dfrac{(T+v)(T+1+v)}{2}),这里有一个稍微有些棘手的地方,就是 (2) 不一定有逆元,因此不能直接拆成 (v) 的平方项、(v) 的一次项和常数项分别求和再乘上 (2) 的逆元,因此我们采用这样一个方法:(dfrac{(T+v)(T+1+v)}{2}=dfrac{T(T+1)}{2}+Tv+dfrac{v(v+1)}{2}),这样三项都是整数,就可以直接求和了,维护 (dp_{i,v},v·dp_{i,v},dfrac{v(v+1)}{2}·dp_{i,v}) 的前缀和即可 (mathcal O(1)) 计算上式。

    时间复杂度 (n^3),代码不算难写。

    const int MAXN=500;
    const int MAXM=500*499/2;
    int n,mod,ans=0,dp[MAXN+5][MAXM+5];
    int sum[MAXM+5],_sum[MAXM+5],__sum[MAXM+5];
    int c[MAXN+5][MAXN+5],fac[MAXN+5];
    int sum0(int l,int r){if(l>r) return 0;return (sum[r]-((!l)?0:sum[l-1])+mod)%mod;}
    int sum1(int l,int r){if(l>r) return 0;return (_sum[r]-((!l)?0:_sum[l-1])+mod)%mod;}
    int sum2(int l,int r){if(l>r) return 0;return (__sum[r]-((!l)?0:__sum[l-1])+mod)%mod;}
    int main(){
    	scanf("%d%d",&n,&mod);dp[1][0]=1;
    	for(int i=0;i<=MAXM;i++) sum[i]=1;
    	for(int i=2;i<=n;i++){
    		for(int j=0;j<=i*(i-1)/2;j++){
    			dp[i][j]=sum[j];
    			if(j-i>=0) dp[i][j]=(dp[i][j]-sum[j-i]+mod)%mod;
    //			printf("%d %d %d
    ",i,j,dp[i][j]);
    		} memset(sum,0,sizeof(sum));sum[0]=dp[i][0];
    		for(int j=1;j<=MAXM;j++) sum[j]=(sum[j-1]+dp[i][j])%mod;
    	}
    	for(int i=(fac[0]=1)-1;i<=MAXN;i++){
    		c[i][0]=1;if(i) fac[i]=1ll*fac[i-1]*i%mod;
    		for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    	}
    	for(int i=0;i<n;i++){
    		int lim=i*(i+1)/2,s=0;
    		memset(sum,0,sizeof(sum));
    		memset(_sum,0,sizeof(_sum));
    		memset(__sum,0,sizeof(__sum));
    		sum[0]=dp[i][0];
    		for(int j=1;j<=i*(i-1)/2;j++){
    			sum[j]=(sum[j-1]+dp[i][j])%mod;
    			_sum[j]=(_sum[j-1]+1ll*j*dp[i][j])%mod;
    			__sum[j]=(__sum[j-1]+1ll*j*(j+1)/2*dp[i][j])%mod;
    		}
    		for(int j=0;j<=i*(i-1)/2;j++){
    			s=(s+1ll*lim*sum[j-1]%mod*dp[i][j])%mod;
    			int T=i-j+1;
    			int A=(1ll*T*(T+1)/2%mod+mod)%mod;
    			int B=(T+mod)%mod;
    			int minus=(1ll*A*sum0(max(0,j-i),j-1)%mod
    					  +1ll*B*sum1(max(0,j-i),j-1)%mod
    					  +sum2(max(0,j-i),j-1))%mod;
    			s=(s-1ll*minus*dp[i][j]%mod+mod)%mod;
    		} //printf("%d
    ",s);
    		ans=(ans+1ll*s*c[n][n-i-1]%mod*fac[n-i-1])%mod;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Linux下NDK编译FFMPEG包含neon参数
    编译器优化陷阱——全局指针多次使用异常
    Linux下使用NDK编译FFMPEG(libstagefright)
    查看Android支持的硬解码信息
    图片格式转换
    转 MFC 主界面函数中线程等待避免界面卡死的处理方法
    Windows Shell编程实现重叠图标IconOverlay
    转 MFC中 GB2312、UTF-8、unicode 之间转换
    windows双机调试
    位图BITMAP结构
  • 原文地址:https://www.cnblogs.com/ET2006/p/codeforces-1542E2.html
Copyright © 2011-2022 走看看