[noip模拟题]排队
【问题描述】
小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的值。
【输入输出样例】
lineup.in |
lineup.out |
5 3 |
15 |
【数据范围】
对于20%的数据,有n≤10,k≤40;
对于60%的数据,有n≤100,k≤500;
对于100%的数据,有n≤100,k≤n*(n-1)/2。
这道题并不是特别地难,其实只需要这样分析,假设把第i个同学加入前面(i-1)个同学组成的有j‘组逆序对的队列中(假设加入的同学的大小从低到高)
,这时i同学插在最后面不会增加逆序对的个数,但是插在第k(从后往前数)位同学前面会增加k个逆序对(第i个同学身高最高),如下:
原队列: 1 4 2 3
将第5个同学插入第2(k = 3)位同学前: 1 5 4 2 3
上面褐色就是和这个数产生的逆序对的个数。
那么达到一个状态(i,j)就是第(i- 1)个人组成的队列所有能够通过插入第i个同学得到的j个逆序对
这么说也存在不可能的时候
比如原队列: 1 2 3
插入进去后要使逆序对的数量增加到4个,要使增加的逆序对的个数最大,即将第4位同学增加到队首,增加3个逆序对,0 + 3 < 4
所以这种情况是不可能的
于是还要满足这个条件(假设之前这个逆序对的个数为k) k + i - 1 > j
得到了这个递推式(f的初值为0)(如果下表为负也不用管)
f[i][j] = f[i - 1][j] + f[i - 1][j - 1] +...+ f[i - 1][j - i + 1]
貌似这样的时间复杂度是O(kn2)对于100这样的范围还是可以过的
不过明明是可以再继续优化,就只用个前缀和就可以轻松代替第三重循环,也可以化简一下上面的递推式
,总之有很多方法可以消去第三重循环
Code
1 #include<iostream> 2 #include<fstream> 3 #define moder 1799999 4 using namespace std; 5 ifstream fin("lineup.in"); 6 ofstream fout("lineup.out"); 7 int f[101][5051]; 8 long long s[101][5051]; 9 int n,k; 10 int buf; 11 void init_dp(){ 12 for(int i = 2;i <= n;i++){ 13 f[i][0] = 1; 14 s[i][0] = 1; 15 } 16 } 17 void dp(){ 18 f[2][1] = 1; 19 s[2][1] = s[2][0] + 1; 20 for(int i = 3;i <= n;i++){ 21 buf = (i - 1)*(i - 2)/2; 22 for(int j = 1;j < i ;j++){ 23 f[i][j] = s[i - 1][min(j,buf)]; 24 s[i][j] = (s[i][j - 1] + f[i][j]) % moder; 25 } 26 for(int j = i ;j <=i*(i - 1)/2;j++){ 27 f[i][j] = (s[i - 1][min(buf,j)] - s[i - 1][j - i] + moder) % moder; 28 s[i][j] = (s[i][j - 1] + f[i][j]) % moder; 29 } 30 } 31 } 32 int main(){ 33 fin>>n>>k; 34 init_dp(); 35 dp(); 36 fout<<f[n][k]; 37 return 0; 38 }