【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 \binom{n}{2}$

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

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

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

$f(n)={\prod\limits_{i=1}^n(1-x^i)\over(1-x)^n}$

下面那个东西很好求,${1\over (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的方案数$\times(-1)^j$。

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

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

如何构造出所有的上升序列呢?我们考虑将这个序列逆向差分$(b_i=a_i-a_{i+1})$,于是这个序列的总和就变成了$\sum\limits_{k=1}^jb_k\times 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(k\sqrt{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;
}

 

转载于:https://www.cnblogs.com/CQzhangyu/p/8097647.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值