wave

题意:求有多少个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\n", 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 \n", 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 }
AC代码

空间不够,要滚动。然后每次要清零。当前价值可能为负所以要加一个偏移量。

相同的套路还有相邻的两个数贡献为max/min,几乎一模一样。

posted @ 2018-10-24 17:23  huyufeifei  阅读(...)  评论(...编辑  收藏