衝刺 NOIP 400pts + 之神仙專題

HANGRY_Sol&Cekas發表於2024-03-29

衝刺專題之 \(DP\)

\(T_A\) Helping People

$$codeforces$$

題意

給定一個長為 \(n\) 序列 \(A\) , 其中有 \(q\) 個區間 \([l , r]\) 定義為 \(B_i\) , 對於每一個區間 , 其有 \(P_i\) 的機率使 \(A\) 序列從 \(l\)\(r\) 之間的數增加 \(1\) . 問最後整個區間的最大值的期望。其中 , \(n \le 10^5 , q \le 5 \times 10^3\)

對於 \(\forall B_i , B_j (1 \le i , j \le q 且 i \ne j)\) $B_i \cap B_j = \varnothing 或 B_i \subseteq B_j 或 B_j \subseteq B_i $ (即對於兩個區間來說,其不相交或被包含)

樣例輸入,輸出

3 5
1 2 3
1 3 0.500
2 2 0.250
1 2 0.800
1 1 0.120
2 2 0.900
4.465000000

本題為 \(SPJ\) , 只要輸出與正解之差 \(\le 10^{-6}\) 即可透過。

題解

一言難盡。

現在我們發現這個區間的性質完全滿足建樹的條件,於是我們建樹,並跑樹形 \(DP\)

並設一個超級原點 \(0\)\([1 , n]\) , 其增加的機率為 \(0\)

我們設 \(front_i\) 為區間 \(i\) 的區間最大值。

現在考慮設 \(dp_{i , j}\) 為第 \(i\) 號節點 , 最大值為 \(front_i + j\)\(\bf{機率}\) (本題的期望就是個幌子)

\(sum_{i , j}\)\(dp_i\) 陣列的字首和。即 \(\le front_i + j\) 的總機率。

若我們列舉到 \(x\) 號節點 :


第一步

我們現在不考慮 \(x\) 會增加 \(1\) :

\(mid_{i , j}\) 陣列的意義為第 \(i\) 號節點 , 不考慮 \(i\) 會增加 \(1\) , 最大值為 \(front_i + j\) 的機率 .

得到初步轉移:

定義 \(SUM\) 初為 : \(\prod\limits_{z \in son_x}sum[ \ z \ ][ \ front_x + i - front_z \ ]\)

\[mid[ \ x \ ][ \ i \ ] = \sum_{y \in son_x }\frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] } \times mid[ \ y \ ][ \ front_x + i - front_y \ ] \ \ \ (i \in [ \ 0 \ , \ q \ ]) \]

\[SUM -= \frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] } \times mid[ \ y \ ][ \ front_x + i - front_y \ ] \]

(對於每一個 \(y\) 的運算之後 \(SUM\) 進行此次運算 )

當然,如果 \(sum[ \ y \ ][ \ front_x + i - front_y \ ] = 0\) 的話 , 此時直接為 \(0\) (別問,問就是沒注意錯了 \(114514\) 遍,每次 \(\color{red}{WA}\) \(Test \ 20\) )

考慮此式的正確性:

\(\prod\limits_{z \in son_x}sum[ \ z \ ][ \ front_x + i - front_z \ ]\) : 這個東西意味著 對於 \(x\) 的每一個兒子 , 取的值均 \(\le front_x + i\) 的機率 ;

\(\frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] }\) : 是不考慮 \(y\) 區間 , 其他區間最大值均 \(\le front_x + i\) 的機率 ;

我們 \(\times mid[ \ y \ ][ \ front_x + i - front_y \ ]\) 保證了整個 \(x\) 區間 最大值為 \(y\) 區間的 \(front_x + i\)

但如果 \(SUM\) 不變的話 , 對於兩個區間 \(y , z \in son_x\) 均有最大值是 \(front_x + i\) 的情況時,

那麼 \(y , z\) 均為 \(front_x + i\) 時的機率將會被計算兩次。

此句減的操作是去重的過程。


第二步

我們進行考慮父區間 \(x\) 對於答案的影響。

我們發現當 \(x\) 的最大值和其某個子區間相同時 , \(i = 1 | \ | 0\) 的時候需要分討一下 (原因下面提到)

對於 \(i \in [2 \ , \ q]\) 時顯然不用再考慮那些被 \(x\) 包含卻未被 \(y \in son_x\) 包含的點中有最大值。因為即使 \(x\) 選 , 也只會把上面的點變為 \(front_x + 1\) ,不會變為 \(front_x + i\) .

我們得到這樣的式子:

\[dp[ \ x \ ][ \ i \ ] = mid[ \ x \ ][ \ i \ ] \times \left( 1 - P[ \ x \ ] \right) + mid[ \ x \ ][ \ i - 1 \ ] \times P[x] \]

顯然

但是對於 $ i = 1 , i = 0$ 情況 , 我們需要對 \(x\) 區間進行分討了。

如果 \(x\) 區間的最大值不在其子節點區間裡:

如果 \(i = 1\) 時 , \(x\) 區間進行加一時 , 變為 \(front_x + 1\) , 最大值可能是他 , 所以這個就保證了區間最大值為 \(front_x + 1\) , 所以剩下的子區間可選的得到的機率為 :

\[dp[ \ x \ ][ \ 1 \ ] = P[ \ x \ ] \times \sum_{y \in son_x }sum[ \ y \ ][ \ front_x - front_y \ ] \]

注意你會 \(+ 1\) 的 , 所以不能超出 \(front_x\) .

對於 \(i = 0\) 時 , 因為最大值就在已定了,所以剩下的只要選的不超過 \(front_x\) 就可以了.

\[dp[ \ x \ ][ \ 0 \ ] = \left(1 - P[ \ x \ ]\right) \times \sum_{y \in son_x }sum[ \ y \ ][ \ front_x - front_y \ ] \]

如果 \(front_x\) 出現在其子區間裡:

易得:

\[dp[ \ x \ ][ \ 1 \ ] = mid[ \ x \ ][ \ 0 \ ] \times P[ \ x \ ] + mid[ \ x \ ][ \ 1 \ ] \times \left( 1 - P[ \ x \ ]\right) \]

\[dp[ \ x \ ][ \ 0 \ ] = mid[ \ x \ ][ \ 0 \ ] \times \left(1 - P[ \ x \ ] \right) \]

\(code\)

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 1e5 + 100 ; 
const int M = 5e3 + 100 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 
 
    while ( c < '0' || c > '9' ) {
        f = c == '-' ? -f : f ; 
        c = getchar() ; 
    }
 
    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }
 
    return x * f ; 
}
 
int st[N][20] , lg[N] ; 
inline void build_ST( int n ) {
    for ( int i = 2 ; i <= n ; ++ i ) {
        lg[i] = lg[i >> 1] + 1 ; 
    }
 
    for ( int j = 1 ; j <= lg[n] ; ++ j ) {
        for ( int i = 1 ; i + ( 1 << j ) - 1 <= n ; ++ i ) {
            st[i][j] = max( st[i][j - 1] , st[i + ( 1 << ( j - 1 ) )][j - 1] ) ; 
        } 
    }
}
inline int MAX_ST( int l , int r ) {
    int k = lg[r - l + 1] ; 
    return max( st[l][k] , st[r - ( 1 << k ) + 1][k] ) ; 
}
 
class edge {
    public:
        int to , next ; 
}e[M] ; int head[M] , cnt ; bool vis[M] ; 
inline void add( int x , int y ) {
    cnt ++ ; 
    e[cnt].to = y ; 
    e[cnt].next = head[x] ; 
    head[x] = cnt ; 
}
 
int n , q ; 
double dp[M][M] , sum[M][M] ; 
class Node {
    public: 
        int l , r , front ; 
        double p ; 
}b[M] ; 
bool cmp( Node a , Node b ) {
    if ( a.l == b.l ) return a.r > b.r ; 
    return a.l < b.l ; 
}
 
void dfs_find( int x ) {
    for ( int i = x + 1 ; i <= q ; ++ i ) {
        if ( b[x].r < b[i].l ) break ; 
 
        if ( b[x].l <= b[i].l && b[i].r <= b[x].r && !vis[i] ) {
            vis[i] = 1 ; 
            add(x,i) ; dfs_find(i) ; 
        }
    }
}

void dfs( int x ) {
    bool flag = 0 ;
    for ( int i = head[x] ; i ; i = e[i].next ) {
        dfs(e[i].to) ; 
        
        if ( b[x].front == b[e[i].to].front ) flag = 1 ; 
    }
    long double sum0 = 1 ;  bool flak = 0 ; 

    for ( int i = 0 ; i <= q ; ++ i ) {
        long double sumi = 1 ; flak = 0 ; 

        for ( int j = head[x] ; j ; j = e[j].next ) {
            int y = e[j].to ; 

            if ( b[x].front + i - b[y].front <= q ) 
                if ( sum[y][b[x].front + i - b[y].front] > 0 )
                    sumi *= sum[y][b[x].front + i - b[y].front] ; 
                else flak = 1 ; 
        }

        if ( !flak )
            for ( int j = head[x] ; j ; j = e[j].next ) {
                int y = e[j].to ; 
                
                if ( b[x].front + i - b[y].front <= q ) 
                    if ( sum[y][b[x].front + i - b[y].front] > 0 ) {
                        dp[x][i] += sumi / sum[y][b[x].front + i - b[y].front] * dp[y][b[x].front + i - b[y].front] ; 
                        sumi -= sumi / sum[y][b[x].front + i - b[y].front] * dp[y][b[x].front + i - b[y].front] ; 
                    }
            }
    }
    
    for ( int i = q ; i >= 2 ; -- i ) {
        dp[x][i] = dp[x][i - 1] * b[x].p + dp[x][i] * ( 1 - b[x].p ) ; 
    }

    if ( !flag ) {
        long double sum_0 = 1 ; 
        for ( int i = head[x] ; i ; i = e[i].next ) {
            int y = e[i].to ; 

            if ( b[x].front - b[y].front <= q ) {
                sum_0 *= sum[y][b[x].front - b[y].front] ; 
            }
        }

        dp[x][1] = ( 1 - b[x].p ) * dp[x][1] + b[x].p * sum_0 ; 
        dp[x][0] = ( 1 - b[x].p ) * sum_0 ; 
    }
    else {
        dp[x][1] = dp[x][0] * b[x].p + dp[x][1] * ( 1 - b[x].p ) ;    
        dp[x][0] = dp[x][0] * ( 1 - b[x].p ) ; 
    }

    sum[x][0] = dp[x][0] ; 
    for ( int i = 1 ; i <= q ; ++ i ) {
        sum[x][i] = sum[x][i - 1] + dp[x][i] ; 
    }
}
 
inline void Query_Expect() {
    long double ans = 0 ; 
    int maxl = b[0].front ; 

    for ( int i = 0 ; i <= q ; ++ i ) {

        ans += ( maxl + i ) * dp[0][i] ; 
    }
 
    printf( "%.9Lf\n" , ans ) ; 
}
 
signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out", "w" ,stdout ) ; 
#endif 
    
    n = read() ; q = read() ; 
    for ( int i = 1 ; i <= n ; ++ i ) {
        st[i][0] = read() ; 
    }

    build_ST( n ) ; 
 
    for ( int i = 1 ; i <= q ; ++ i ) {
        b[i].l = read() ; b[i].r = read() ; 
        b[i].front = MAX_ST( b[i].l , b[i].r ) ; 
        scanf( "%lf" , &b[i].p ) ; 
    }

    sort( b + 1 , b + q + 1 , cmp ) ; 
    b[0].l = 1 , b[0].r = n , b[0].p = 0 ; vis[0] = 1 ; 
    b[0].front = MAX_ST( 1 , n ) ; 

    dfs_find(0) ; dfs(0) ; 

    Query_Expect() ; 
}