bzoj2817: [ZJOI2012]波浪
分析
比较烦的dp题
首先这个相邻两项差的绝对值,这个绝对值很烦对吧,于是根据常规操作,我们肯定从小到大放数。
因为如此,我们考虑放一个数对答案的贡献。
这个时候,我们假设原来已经放了若干个数,我们发现,当前这个数对答案的贡献和原来放了什么数已经没有关系了。
考虑放数的方式,假设当前数为
i
i
,显然有
- 放在一个数旁边。
- 放在两个数旁边
- 没有放在任何一个数的旁边。
第一种情况,原来的数比当前的数小,之后放的数比当前的数大,所以贡献是
第二种情况,旁边的两个数都比当前的数小,所以贡献是
2i
2
i
如果没有放在任何数旁边,之后放在这个数后面的数一定比当前的数更大。所以这个数的贡献就是
−2i
−
2
i
这个时候我们就要考虑这三种方案分别有多少个位置。而这些个位置其实是由之前的连通块个数决定的。就是放了几“团”数
第一种情况,假设原来的连通块个数为
k
k
,可以放的数的位置个数就是,新的状态连通块个数仍然是
k
k
第二种情况位置个数是,新的状态连通块个数是
k−1
k
−
1
(合并了两个连通块)
第三种情况位置个数是
k+1
k
+
1
,新的状态连通块个数是
k+1
k
+
1
(新建了一个连通块)
注意这里新建指的是相对位置,也就是插入。
这个时候我们发现还有另外一个问题,就是如果连通块放到了边界上,情况会不太一样,但其实问题不大,是类似的。
下面给出两个边界都没有连通块的情况,其余方程可参照代码。
Condition1
fi,j,k=fi−1,j,k⋅2k f i , j , k = f i − 1 , j , k ⋅ 2 k
Condition2
fi,j+i,k−1=fi−1,j,k⋅(k−1) f i , j + i , k − 1 = f i − 1 , j , k ⋅ ( k − 1 )
Condition3
fi,j−i,k+1=fi−1,j,k⋅(k+1)
f
i
,
j
−
i
,
k
+
1
=
f
i
−
1
,
j
,
k
⋅
(
k
+
1
)
至于边上有联通快的情况,方程自己推一推就出来了,详见代码。
还有这道题比较恶心卡了精度,要用float128搞,而float128贼慢,所以按照数据分类用double和float即可。
代码
/**************************************************************
Problem: 2817
User: 2014lvzelong
Language: C++
Result: Accepted
Time:15180 ms
Memory:128800 kb
****************************************************************/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define area [2][9005][101][3]
const int z = 4500, mx = 9000;
__float128 f area; double g area;
int n, m, k, top, st[10001];
template <class T> void print(T ans, int p) {
long long i = (long long) ans; ans -= i;
for(top = 0; i ; i /= 10) st[++top] = i % 10;
for(int i = 1; i <= (top >> 1); ++i) std::swap(st[i], st[top - i + 1]);
if(!top) st[++top] = 0; int pos = top + 1;
for(int i = 1; i <= p; ++i, ans -= (st[++top] = (int)ans)) ans *= 10;
ans *= 10; int c = (int)ans; if(c >= 5) ++st[top]; c = top;
for(;st[c] == 10 && c; ++st[--c]) st[c] = 0;
if(!c) putchar('1'); for(int i = 1;i <= top; putchar('0' + st[i++])) if(i == pos) putchar('.');
putchar('\n');
}
template <class T> void Solve(T f area) {
f[0][z - 2][1][0] = 1; f[0][z - 1][1][1] = 2;
f[0][z][1][2] = 1; int cu = 1;
for(int i = 2;i <= n; ++i, cu ^= 1) {
memset(f[cu], 0, sizeof(f[cu])); T tmp;
for(int j = 0;j <= (z << 1); ++j)
for(int k = 1;k <= n - 1; ++k)
for(int tp = 0;tp <= 2; ++tp)
if(tmp = f[cu ^ 1][j][k][tp]) {
if(j + (i << 1) <= mx) f[cu][j + (i << 1)][k - 1][tp] += tmp * (k - 1); // Merge
if(j - (i << 1) >= 0) f[cu][j - (i << 1)][k + 1][tp] += tmp * (k + 1 - tp); // New
f[cu][j][k][tp] += tmp * (k * 2 - tp); // two side
if(tp <= 1) {
if(j - i >= 0) f[cu][j - i][k + 1][tp + 1] += tmp * (2 - tp); // New
if(j + i <= mx) f[cu][j + i][k][tp + 1] += tmp * (2 - tp); // one side
}
}
}
T ans = 0;
for(int i = m + z;i <= mx; ++i) ans += f[cu ^ 1][i][1][2];
for(int i = 1;i <= n; ++i) ans /= (T)i;
print(ans, k);
}
int main() {
scanf("%d%d%d", &n, &m, &k);
if(k <= 8) Solve(g);
else Solve(f);
return 0;
}