题目传送门
强烈吐槽一下这道题不采用取模的方式,害我被卡精度卡了半天。
因为毕竟是是自己yy的一种垃圾做法
手玩几组数据~~(或者左转看看题解)~~发现按层划分BFS序很科学。
然后就可以发现一个结论:一种合法的BFS序的划分对应一棵树。
证明:考虑递归构造对应关系。对于某个BFS序,如果我们已经构造出了 i i i层以内的树,考虑第 i + 1 i+1 i+1层,因为已经划分好了,所以这一层有那些节点我们是知道的。如果我们将 i i i层以内的节点在DFS序中全部删掉,剩下的一定是DFS序中的若干个区间。DFS序可以看成子树根+子树的形式,那么每个区间就对应一个子树,并且子树根就是区间左端点前一个节点。这样的话 B F S BFS BFS序中第 i + 1 i+1 i+1层的节点就可以去对应区间找他们的父亲。
那么现在问题转化成合法地划分 B F S BFS BFS序。考虑 D p Dp Dp
f i , d f_{i,d} fi,d表示划分到第 i i i个数,一共划分了 d d d层的方案。
f i , d = ∑ j = 1 i − 1 f j , d − 1 [ c a n ( j , i ) ] f_{i,d}=\sum_{j=1}^{i-1}f_{j,d-1}[can(j,i)] fi,d=∑j=1i−1fj,d−1[can(j,i)]
c a n ( j , i ) can(j,i) can(j,i)表示 [ j + 1 , i ] [j+1,i] [j+1,i]这个划分是合法的。
考虑如何求出 c a n ( j , i ) can(j,i) can(j,i)
在构造的时候实际上已经初步得出了一个层划分合法性的判定方法。
首先将BfS序中 j j j以前的点删掉。
根据题目的要求,第一个条件就是BfS序中 [ j + 1 , i ] [j+1,i] [j+1,i]中的点在DFS序中的位置递增。
而构造的时候发现,删去的区间左端点一定是子树根,所以必须出现在划分中。
如果暴力枚举 i , j i,j i,j,然后 O ( n ) O(n) O(n)判断,这样的求 c a n can can复杂度是 O ( n 3 ) O(n^3) O(n3)的,而 D p Dp Dp也是 O ( n 3 ) O(n^3) O(n3)的,这样就得到了一个 O ( n 3 ) O(n^3) O(n3)的算法,接下来考虑优化。
首先,往 O ( n 2 ) O(n^2) O(n2)进发,先优化预处理 c a n ( j , i ) can(j,i) can(j,i)。
先枚举一个 j j j,然后枚举 i i i,对于第2个条件,我们对于 D F S DFS DFS序中每个区间左端点在 B F S BFS BFS序中的最大位置 l l l,那么合法的 i i i一定在 l l l之后。
然后从 l l l开始扫描一段区间,这段区间满足条件1,也就是位置递增即可。假设扫到了 r r r,那么合法 i i i在 [ l , r ] [l,r] [l,r]之间。
对于 f f f,一种比较有技巧性的做法是用生成函数优化。
F i ( x ) = ∑ f i , d x d F_i(x)=\sum f_{i,d}x^d Fi(x)=∑fi,dxd
这样的话方程可以写成 F i = ∑ x F j [ c a n ( j , i ) ] F_i=\sum xF_j[can(j,i)] Fi=∑xFj[can(j,i)]
事实上,我们要求的东西是 F n ′ ( 1 ) F n ( 1 ) \frac{F'_n(1)}{F_n(1)} Fn(1)Fn′(1)
求个导看看:
F i ′ = x ∑ F j ′ [ c a n ( j , i ) ] + ∑ F j [ c a n ( j , i ) ] F_i'=x\sum F_j'[can(j,i)]+\sum F_j[can(j,i)] Fi′=x∑Fj′[can(j,i)]+∑Fj[can(j,i)]
将 x = 1 x=1 x=1带入:
F i ( 1 ) = ∑ F j ( 1 ) [ c a n ( j , i ) ] F_i(1)=\sum F_j(1)[can(j,i)] Fi(1)=∑Fj(1)[can(j,i)]
F i ′ ( 1 ) = ∑ F j ′ ( 1 ) [ c a n ( j , i ) ] + ∑ F j ( 1 ) [ c a n ( j , i ) ] F'_i(1)=\sum F'_j(1)[can(j,i)]+\sum F_j(1)[can(j,i)] Fi′(1)=∑Fj′(1)[can(j,i)]+∑Fj(1)[can(j,i)]
设 f i = F i ( 1 ) , g i = F i ′ ( 1 ) f_i=F_i(1),g_i=F'_i(1) fi=Fi(1),gi=Fi′(1)转移就行了。
其实这个东西可以直接用期望去推,但是人菜就只好用笨方法推方程咯!
所以成功地把 D p Dp Dp也优化到了 O ( n 2 ) O(n^2) O(n2)
还可以优化吗?
之前发现的一个性质是,对于一个 j j j,它仅仅会转移到一段特定区间 [ l , r ] [l,r] [l,r]内的 i i i。而且贡献是线性的。
那么显然 D p Dp Dp部分差分打标记前缀和就 O ( n ) O(n) O(n)了。
而 c a n ( j , i ) can(j,i) can(j,i)怎么办?
对于某个 j j j,显然 r r r是可以用指针扫过去的。除非当前的 j j j越过了 r r r,不然 r r r不会动。
而 l l l可以在删除数的时候记录一下删除位置的下一个位置对应BFS序的位置,取个 m a x max max即可。
这样的话两把指针扫一扫也是线性的了。这道题就这么被胡乱优化一波搞定了!
结果被卡精度了?!!!
开__float128也不行?!!!!
我搞了一波骚操作,把某个数科学计数法表示,然后重载加减法,忽略数量级超过一百的运算,然后就过了。。。。
#include
const int N = 2e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
struct Data {
double x; int e;
}g[N], f[N];
Data operator + (Data a, Data b) {
if(a.e < b.e)
std::swap(a, b);
int d = a.e - b.e; double l = b.x;
if(d > 100) l = 0;
else {
for(;d--;)
l /= 10;
}
a.x += l;
if(fabs(a.x) > 10) a.x /= 10, ++a.e;
return a;
}
void operator += (Data &a, Data b) {a = a + b;}
void operator -= (Data &a, Data b) {
b.x = -b.x;
a = a + b;
}
int a[N], b[N], psa[N], psb[N], n, must;
int main() {
n = ri();
for(int i = 1;i <= n; ++i)
a[i] = ri(), psa[a[i]] = i;
for(int i = 1;i <= n; ++i)
b[i] = ri(), psb[b[i]] = i;
int l = 0, r = 0;
f[1].x = g[1].x = 1;
f[2].x = g[2].x = -1;
for(int j = 1, i;j <= n; ++j) {
f[j] += f[j - 1]; g[j] += g[j - 1];
if(j == n) break;
l = std::max(l, psb[a[psa[b[j]] + 1]]);
for(r = std::max(r, j + 2);r <= n; ++r)
if(psa[b[r]] < psa[b[r - 1]])
break;
--r;
if(l <= r) {
f[l] += f[j]; f[r + 1] -= f[j];
g[l] += f[j] + g[j]; g[r + 1] -= f[j] + g[j];
}
}
int d = g[n].e - f[n].e; double x = g[n].x / f[n].x;
for(;d--;)
x *= 10;
printf("%.3lf\n", x);
return 0;
}