题意
求(1...n)的所有排列中有多少个排列含有(k)个逆序对.答案对(10^4)取模.
(1le n,kle1000).
思路1 暴力
暴力枚举全排列,树状数组求逆序对.
时间复杂度(O(n! imes nlog n)).
#include<bits/stdc++.h>
const int SIZE=15;
int n,m,C[SIZE],T[SIZE],Ans;
bool mk[SIZE];
void Change(int i,int x)
{
for(;i<=n;i+=i&(-i))
T[i]+=x;
}
int sum(int i)
{
int x=0;
for(;i;i-=i&(-i))
{
x+=T[i];
}
return x;
}
int Do()
{
memset(T,0,sizeof(T));
int x=0;
for(int i=1;i<=n;i++)
{
x+=sum(n)-sum(C[i]);
Change(C[i],1);
}
return x;
}
void DFS(int step)
{
if(step==n+1)
{
if(Do()==m)++Ans;
return;
}
for(int i=1;i<=n;i++)
{
if(!mk[i])
{
mk[i]=1;
C[step]=i;
DFS(step+1);
mk[i]=0;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
DFS(1);
printf("%d",Ans);
return 0;
}
思路2 动态规划
设(DP[i,j])表示(1...i)的所有排列中,逆序对数为(j)的排列个数.
考虑如何由(DP[i,...])向(DP[i+1,...])转移.
容易发现,这个转移的含义是,把数字(i+1)插入到一个(1...i)的排列中的某个位置,所带来的逆序对数的改变.
由于(i+1)比(1...i)中任何一个数都要大,那么(i+1)与插入的位置以后的所有数,一定形成新的逆序对,与插入的位置以前的所有数,一定不形成新的逆序对.
换句话说,如果我们把(i+1)插入到该排列的第(k)个数后面,那么新形成(i-)个k逆序对.
考虑枚举(k),那么有:
[DP[i,j]=sum_{k=j-(i-1)}^{j}DP[i-1,k]
]
于是可以枚举(i,j,k),求出所有(DP[])的值,总时间复杂度(O(nk^2)).
#include<bits/stdc++.h>
const int SIZE=1005,Mod=10000;
int n,m,DP[SIZE][SIZE];
int main()
{
scanf("%d%d",&n,&m);
DP[1][0]=1;
for(int i=2;i<=n;i++)
for(int k=0;k<=m;k++)
for(int p=std::max(k-(i-1),0);p<=k;p++)
{
DP[i][k]+=DP[i-1][p];
DP[i][k]%=Mod;
}
printf("%d",DP[n][m]);
return 0;
}
思路3 动态规划
发现思路 2 中最内层循环是一段区间和,可以用前缀和快速求出.时间复杂度降至(O(nk)).
#include<bits/stdc++.h>
const int SIZE=1005,Mod=10000;
using namespace std;
int n,m,DP[SIZE][SIZE],sum[SIZE];
int main()
{
scanf("%d%d",&n,&m);
DP[1][0]=1;
for(int k=0;k<=m;k++)
sum[k]=1;
for(int i=2;i<=n;i++)
{
for(int k=0;k<=m;k++)
{
int p=max(k-(i-1),0)-1,Tem;
if(p>=0)Tem=sum[p];
else Tem=0;
DP[i][k]=(sum[k]-Tem+Mod)%Mod;
}
sum[0]=DP[i][0];
for(int k=1;k<=m;k++)
sum[k]=(sum[k-1]+DP[i][k])%Mod;
}
printf("%d",DP[n][m]);
return 0;
}