逆序对的dp

求逆序对:关于逆序对的DP(和归并没半毛关系)
1ff 
我们说(i,j)是a1,a2,…,aN的一个逆序对当且仅当i<j且ai>a j。例如2,4,1,3,5的逆序对有3个,分别为(1,3),(2,3),(2,4)。现在已知N和K,求1…N的所有特定排列,这些排列的逆序对的数量恰好为K。输出这些特定排列的数量。
例如N=5,K=3的时候,满足条件的排列有15个,它们是:
1,2,5,4,3    1,3,4,5,2   1,3,5,2,4   1,4,2,5,3   1,4,3,2,5   1,5,2,3,4    2,1,4,5,3   2,1,5,3,4   2,3,1,5,4   2,3,4,1,5
2,4,1,3,5    3,1,2,5,4   3,1,4,2,5   3,2,1,4,5   4,1,2,3,5
  输入输出格式 Input/output
输入格式:
输入第一行有两个整数N和K。(N≤100,K≤N*(N-1)/2)
输出格式:
将1…N的逆序对数量为K的特定排列的数量输出。为了避免高精度计算,请将结果mod 10000以后再输出!
  输入输出样例 Sample input/output
样例测试点#1
输入样例:  在线IDE
5 3
输出样例:
15


构造f[i,j]表示(1..i)的全排列中,逆序对为j个的排列共有几个
我们首先手动模拟打表一下
f[1,0]=1    f[2,0]=1    f[3,0]=1    f[4,0]=1
                f[2,1]=1    f[3,1]=2    f[4,1]=3
                                f[3,2]=2    f[4,2]=5
                                f[3,3]=1    f[4,3]=6
                                                f[4,4]=5
                                                f[4,5]=3
                                                f[4,6]=1
表中有很多性质 
比如:i的全排列中逆序对的个数是0..i*(i-1)/2:其实这是因为i的全排列中总共有i*(i-1)/2个数对,当这些数对全都是逆序对的时候,就得到了逆序对的最大值,
再比如:对于一个i,他的f[i,j]是关于f[i,i*(i-1)/4]呈现中心对称的(f[4,2]和f[4,4]、f[4,1]和f[4,5]、f[4,0]和f[4,6]关于f[4,3]对称):其实这是因为数对总数为i*(i-1)/2,而一个数对要么是逆序对要么是顺序对,那么我们求i的全排列中逆序对个数有j个,
等价于求i的全排列中顺序对有i*(i-1)/2-j个,显而易见,对于N的全排列   顺序对有K个这样的排列个数,和逆序对有K个这样的排列个数,是相等的于是我们有了f[i,j]=f[i, i*(i-1)/2-j]于是对称中心就是f[i,i*(i-1)/4];
最后一个重要的性质也就是我们的DP状态转移方程就是:
if i>j then f[i,j]:=sum{f[i-1,0],f[i-1,1],f[i-1,2].....f[i-1,j]}
else f[i,j]:=sum{f[i-1,j],f[i-1,j-1],f[i-1,j-2].....f[i-1,j-i+1]}
当i>j的时候,我们枚举i的全排列的第一位的数字,如果是1,那么就要求剩下i-1个数中有j个全排列,如果是2,要求剩下i-1个数中有i-2个全排列,依次类推,得到了第一个方程
当i<=j的时候,由于i的全排列中最大的数字是i,所以把i放到第一位上,由第一位最多能能产生i-1个逆序对,把1放到第一位上能产生0个逆序对,所以i-1-1+1=i-1,这时的f[i,j]就要由f[i-1,j]开始算上他自己,总共i-1项的和。

AC代码:
var f:array[1..100,-1..10000]of longint;
var i,j,n,k:longint;
begin
read(n,k);
fillchar(f,sizeof(f),0);
f[1,0]:=1;f[1,1]:=0;
f[2,0]:=1;f[2,1]:=1;//初始化
for i:=3 to n do
begin
for j:=0 to i*(i-1)div 4 do//从0计算到对称中心
if j<i then//第一种方式转移
f[i,j]:=(f[i,j-1]+f[i-1,j]) mod 10000//因为f[i,j]里面统计的是类似于前缀和的一个东西,f[i,j]只比f[i,j-1]多了f[i-1,j]这一项
else
//第二种方式转移
f[i,j]:=(f[i,j-1]+f[i-1,j]-f[i-1,j-i])mod 10000;//这时求和的项数确定了,就是i-1项,于是f[i,j]比f[i,j-1]多了f[i-1,j]这一项,少了f[i-1,j-i]这一项
for j:=i*(i-1)div 4 +1 to i*(i-1) div 2 do//对称的把剩下的一半copy完
f[i,j]:=(f[i,i*(i-1)div 2 -j])mod 10000;//这里可以不用mod10000
end;
writeln((f[n,k]+10000)mod 10000);//防坑输出,因为有的时候,a、b都是正数,但是a mod b布吉岛怎么是负数了。详见P2267,这道题
end.//如果你得了40分,那么用这样一个防坑输出法,你会惊奇的发现你AC啦



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值