排队(递推)
【问题描述】
小sin所在的班有n名同学,正准备排成一列纵队,但他们不想按身高从矮到高排,那样太单调,太没个性。他们希望恰好有k对同学是高的在前,矮的在后,其余都是矮的在前,高的在后。如当n=5,k=3时,假设5人从矮到高分别标为1、2、3、4、5,则(1,5,2,3,4)、(2,3,1,5,4)、(3,1,4,2,5)都是可行的排法。小sin想知道总共有多少种可行排法。
【输入】
输入文件名为lineup.in。
一行两个整数n和k,意义见问题描述。
【输出】
输出文件名为lineup.out。
输出一个整数,表示可行排法数。由于结果可能很大,请输出排法数mod 1799999的值。
【输入样例1】
5 3
【输出样例1】
15
【数据范围】
对于20%的数据,有n≤10,k≤40;
对于60%的数据,有n≤100,k≤500;
对于100%的数据,有n≤100,k≤n*(n-1)/2。
此题是求逆序对。一开始想到暴力算法——直接枚举,结果只过了一组数据。。。
后来经同学讲解,原来正解是用递推算法,很后悔当时写完枚举后没有用小数据找规律。。。。。。
用递推,f[i,j]表示{an}前i项之和为j的方案数,f[i,j]=f[i-1,j]+f[i-1,j-1]+…+f[i-1,j-i+1]。时间复杂度O(n2k)。
优化:将j变成j-1,可以得出f[i,j-1]=f[i-1,j-1]+…+f[i-1,j-i] 两式相减得:
f[i,j]=f[i-1,j]+f[i,j-1]-f[i-1,j-1]
这也给了我一个教训,写完暴力算法后要学会用小数据找正解的思路。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
long long f[305][5000];
long long n,k;
long long sum=0;
int main()
{
freopen("lineup.in","r",stdin);
freopen("lineup.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++)
f[i][0]=1;
for(int i=1;i<=n;i++)
{
sum+=i-1;
for(int j=1;j<=k;j++)
{
if(j>sum)break;
f[i][j]=(f[i][j-1]+f[i-1][j])%1799999;
if(i<=j)
f[i][j]=(f[i][j]-f[i-1][j-i]+1799999)%1799999;
}
}
cout<<f[n][k]%1799999;
return 0;
}