题意:定义函数(f(A,B) = sum_{i=1}^n max(A_i,B_i)),其中(A)和(B)都是长度为(n)的排列。给出(n)和(k),问有多少对((A,B))满足(f(A,B)geq k)。对(10^9 + 7)取模。
(n leq 50)
首先,可以直接钦定(A)为(1,2...n)的一个排列,即对于所有(i)满足(A_i = i),最后答案再乘以(n!)。
然后就变成了对(B)这一个排列的计数问题。考虑函数(f)中有贡献的只有较大值,我们不必计较其中的较小值具体是什么。这启发我们在dp时把较小的数分配到较大的位置,并记录其数量。
因此,我们令状态(dp_{i,j,k})表示当前从(1)开始放置了(i)个数,其中有(j)个数被分配到后面的位置,并且当前得到的函数值为(k)。当然,这里的函数值只包括(max(A_s,B_s) leq i)的值,这样便于转移。同样地,当我们把一个数分配到后面的位置上时,我们不能轻易乘上一个系数(可分配的位置个数),因为分配不同的位置对答案的贡献不同。
那么,对于第(i+1)个数以及位置,就有下面这些情况:
- (i+1)放在第(i+1)个位置。那么,j不变,k+=i,且只有一种方案。
- (i+1)放在前(i)个位置,第(i+1)个位置放了小于(i+1)的数。那么,j-=1,k+=2*i,且第(i+1)个数有(j)个位置可放,第(i+1)个位置也有(j)个数来放。因此有(j^2)种方案。
- (i+1)放在前(i)个位置,第(i+1)个位置放了大于(i+1)的数。那么,j不变,k+=i,且第(i+1)个数有(j)个位置可放,放在第(i+1)个位置的数未确定。因此有(j)种方案。
- (i+1)放在后面的位置,第(i+1)个位置放了小于(i+1)的数。那么,j不变,k+=i,且(i+1)未确定放在哪个位置,第(i+1)个位置有(j)个数来放。因此有(j)种方案。
- (i+1)放在后面的位置,第(i+1)个位置放了大于(i+1)的数。那么,j+=1,k不变,且(i+1)放在哪里,第(i+1)个位置放什么都是为确定的。因此只有一种方案。
时间复杂度(O(n^4))。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 55, MOD = (int)(1e9 + 7);
int dp[2][MAXN][MAXN * MAXN];
class LittleElephantAndPermutationDiv1 {
public:
int getNumber( int N, int K );
};
int LittleElephantAndPermutationDiv1::getNumber(int N, int K) {
int p = 1;
memset(dp,0,sizeof dp);
dp[0][0][0] = 1;
for (int i = 1 ; i <= N ; ++ i, p ^= 1) {
memset(dp[p],0,sizeof dp[p]);
for (int j = 0 ; j < i ; ++ j) {
for (int k = 0 ; k <= 2500 ; ++ k) {
if (!dp[p^1][j][k]) continue;
(dp[p][j][k+i] += dp[p^1][j][k]) %= MOD;
if (j > 0) (dp[p][j-1][k+i+i] += 1ll * j * j * dp[p^1][j][k] % MOD) %= MOD;
if (i < N) (dp[p][j][k+i] += 1ll * j * dp[p^1][j][k] % MOD) %= MOD;
if (i < N) (dp[p][j][k+i] += 1ll * j * dp[p^1][j][k] % MOD) %= MOD;
if (i < N) (dp[p][j+1][k] += dp[p^1][j][k]) %= MOD;
}
}
}
p ^= 1;
int ret = 0;
for (int i = K ; i <= 2500 ; ++ i)
(ret += dp[p][0][i]) %= MOD;
for (int i = 1 ; i <= N ; ++ i)
ret = 1ll * ret * i % MOD;
return ret;
}
小结:这个dp的特色在于确定一个排列,从而同时对位置和值的分配dp,这样可以解决一些较复杂的问题。