题意:求有多少个1~n的排列满足:
其中n<=50
解:
贼神的一道题。
如何处理绝对值?
从小到大按顺序放数,可以拆掉绝对值。
如果你放的旁边有个空隙,那么贡献-i,如果旁边有个数,贡献+i
然后你设的是f[i][j][k][s]表示前i个数,有j+1段数(j个间隔),两端点状态为k(0~2分别表示都没有放数,放了一个,放了两个),目前贡献为s的方案数。
所以要求的就是f[n][0][2][m]
考虑转移:我们可以把i放在某个间隔里,三种情况。还可以放在两端,4种情况。
1 #include <cstdio> 2 #include <algorithm> 3 4 typedef long long LL; 5 const int N = 53, MO = 1e9 + 7, D = 1500; 6 7 int n; 8 LL f[2][N][3][3000]; 9 10 inline void add(LL &a, const LL &b) { 11 a += b; 12 while(a >= MO) { 13 a -= MO; 14 } 15 while(a < 0) { 16 a += MO; 17 } 18 return; 19 } 20 21 int main() { 22 23 freopen("wave.in", "r", stdin); 24 freopen("wave.out", "w", stdout); 25 int m; 26 scanf("%d%d", &n, &m); 27 28 LL ans = 0; 29 30 f[1][0][0][D - 2] = 1; // 1 in mid 31 f[1][0][1][D - 1] = 2; // 1 in edge 32 for(int i = 1; i < n; i++) { 33 for(int j = 0; j <= (n + 1) >> 1; j++) { 34 for(int k = 0; k < 3; k++) { 35 for(int s = 0; s < 3000; s++) { 36 if(!f[i & 1][j][k][s]) { 37 continue; 38 } 39 LL t = f[i & 1][j][k][s]; 40 //printf("%d %d %d %d = %lld ", i, j, k, s - D, t); 41 // f[i][j][k][s] -> ? 42 if(k < 2) { // i+1 in edge 43 add(f[(i + 1) & 1][j][k + 1][s + i + 1], t * (2 - k)); // merge 44 add(f[(i + 1) & 1][j][k][s], t * (2 - k)); // edge 45 add(f[(i + 1) & 1][j + 1][k + 1][s - i - 1], t * (2 - k)); // end 46 if(j + (2 - k) + i < n - 1) { 47 add(f[(i + 1) & 1][j + 1][k][s - ((i + 1) << 1)], t * (2 - k)); // split 48 //printf("%d %d %d %d %d ", i + 1, j + 1, k, s - ((i + 1) << 1) - D, f[i + 1][j + 1][k][s - ((i + 1) << 1)]); 49 } 50 } 51 // i+1 in mid 52 if(j) { 53 add(f[(i + 1) & 1][j][k][s], t * j * 2); // edge 54 add(f[(i + 1) & 1][j - 1][k][s + ((i + 1) << 1)], t * j); // merge 55 if(j + (2 - k) + i < n - 1) { // split 56 add(f[(i + 1) & 1][j + 1][k][s - ((i + 1) << 1)], t * j); 57 } 58 } 59 f[i & 1][j][k][s] = 0; 60 } 61 } 62 } 63 } 64 65 printf("%lld", f[n & 1][0][2][m + D]); 66 return 0; 67 }
空间不够,要滚动。然后每次要清零。当前价值可能为负所以要加一个偏移量。
相同的套路还有相邻的两个数贡献为max/min,几乎一模一样。