zoukankan      html  css  js  c++  java
  • 【LOJ6077】「2017 山东一轮集训 Day7」逆序对 生成函数+组合数+DP

    【LOJ6077】「2017 山东一轮集训 Day7」逆序对

    题目描述

    给定 n,k ,请求出长度为 n的逆序对数恰好为 k 的排列的个数。答案对 109+7 取模。

    对于一个长度为 n 的排列 p ,其逆序对数即满足 i<j 且 pi>pj 的二元组 (i,j)的数量。

    输入格式

    一行两个整数 n,k

    输出格式

    一行,表示答案。

    样例输入

    7 12

    样例输出

    531

    数据范围与提示

    对于 20% 的数据,n,k≤20
    对于 40% 的数据,n,k≤100
    对于 60% 的数据,n,k≤5000
    对于 100% 的数据,$1 leq n, k leq 100000, 1 leq k leq inom{n}{2}$

    题解:本人第一思路是生成函数,但是想了想模数1e9+7没法搞,后来发现这个思路还真的是对的。(还真的有人拿生成函数A了,太神了)

    首先从小到大插入第i个数时,逆序对数可能增加0,1,2,...i-1,所以最终得到的生成函数就是

    $f(n)=1 imes(1+x) imes(1+x+x^2) imes(1+x+x^2+x^3)...$

    $f(n)={prodlimits_{i=1}^n(1-x^i)over(1-x)^n}$

    下面那个东西很好求,${1over (1-x)^n}=(1+x+x^2+...)^n=sum C_{i+n-1}^{n-1}x^i$,然后我们考虑上面那个东西有什么意义。

    你可以理解为第i项的系数是:有n个数,1,2,3...n,从中选出j个数使得总和为i的方案数$ imes(-1)^j$。

    这就大大简化了我们的问题,我们令f[j][i]表示选出j个数总和为i的方案数,显然j是$sqrt{i}$级别的。

    但是我们选出来的j个数并不能重复,所以这个问题还是比较难处理的,我们可以再转化一下,求长度为j,每个数在[1,n]之间,总和为i的上升序列的方案数。

    如何构造出所有的上升序列呢?我们考虑将这个序列逆向差分$(b_i=a_i-a_{i+1})$,于是这个序列的总和就变成了$sumlimits_{k=1}^jb_k imes k$。我们只需要满足$b_k>0$即可。

    这时就容易DP了,f[i][j]可以由这几种状态转移而来:

    如果i>=j,我们可以将bj++,那么f[j][i]+=f[j][i-j];我们还可以在bj后面增加一个1,那么f[j][i]+=f[j-1][i-j]。
    如果i>n,此时可能出现a序列的最后一项>n的情况,即b序列的总和>n的情况,那么f[j][i]-=f[j-1][i-n-1]即可。

    最后统计一下答案即可,时间复杂度$O(ksqrt{k})$

    #include <cstdio>
    #include <iostream>
    #include <cstring>
    using namespace std;
    const int N=100010;
    const int M=450;
    const int P=1000000007;
    typedef long long ll;
    ll jc[N<<1],jcc[N<<1],ine[N<<1];
    ll ans;
    int f[M][N];
    int n,k;
    inline ll c(int a,int b)
    {
    	if(a<b)	return 0;
    	return jc[a]*jcc[b]%P*jcc[a-b]%P;
    }
    inline void upd(int &x,int y)
    {
    	x+=y;
    	if(x>=P)	x-=P;
    }
    int main()
    {
    	scanf("%d%d",&n,&k);
    	int i,j;
    	ine[0]=ine[1]=jc[0]=jc[1]=jcc[0]=jcc[1]=1;
    	for(i=2;i<=n+k;i++)	jc[i]=jc[i-1]*i%P,ine[i]=P-(P/i)*ine[P%i]%P,jcc[i]=jcc[i-1]*ine[i]%P;
    	f[0][0]=1;
    	for(i=1;i<M;i++)
    	{
    		for(j=i;j<=k;j++)
    		{
    			if(j>=i)	upd(f[i][j],f[i][j-i]),upd(f[i][j],f[i-1][j-i]);
    			if(j>n)	upd(f[i][j],P-f[i-1][j-n-1]);
    		}
    	}
    	for(i=0;i<=k;i++)
    	{
    		ll tmp=0;
    		for(j=0;j<M;j++)	tmp+=((j&1)?-1:1)*f[j][i];
    		tmp=(tmp%P+P)%P;
    		ans=(ans+tmp*c(k-i+n-1,n-1))%P;
    	}
    	printf("%lld",ans);
    	return 0;
    }
  • 相关阅读:
    (转)创建DB2实例时出错,请大家帮忙解决
    lscons 命令,设置当前控制台设备的名称写至标准输出
    (转)AIX下修改用户最大进程数
    (转)AIX 5.3 安装中文语言包
    (转)AIX修改系统时区的3种方法和AIX 时间问题(夏令时)
    (转)企业级NFS网络文件共享服务
    一天一个mysql函数(二) FIND_IN_SET()
    sql语句备忘
    一天一个mysql函数(一) cast && convert
    c语言海量数据处理
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/8097647.html
Copyright © 2011-2022 走看看