24/04/13 CF494C Helping People / HDU5866 Lucky E

Iictiw發表於2024-04-13

CF494C:

題面翻譯

有一個長為 \(n\) 的數列,初始時為 \(a_{1..n}\)

給你 \(q\) 個操作,第 \(i\) 個操作將 \([l_i,r_i]\) 內的數全部加一,有 \(p_i\) 的機率被執行。保證區間不會交錯,即:\(\forall i,j\in[1,q],l_i\le r_i<l_j\le r_j\)\(l_i\le l_j\le r_j\le r_i\)\(l_j\le r_j<l_i\le r_i\)\(l_j\le l_i\le r_i\le r_j\)

求操作完成後數列的最大值的期望。

輸入格式

第一行 \(n,\,q\,(1\le n\le10^5,\,1\le q\le 5000)\)

第二行 \(a_1,\,a_2,\,\cdots,\,a_n\,(0\le a_i\le10^9)\)

接下來 \(q\) 行,每行 \(l_i,\,r_i,\,p_i\,(1\le l_i\le r_i\le n,\,0\le p_i\le1)\)

輸出格式

一個實數,表示答案,絕對/相對誤差在 \(10^{-6}\) 內算對。

Translated by ouuan.

題目描述

Malek is a rich man. He also is very generous. That's why he decided to split his money between poor people. A charity institute knows $ n $ poor people numbered from $ 1 $ to $ n $ . The institute gave Malek $ q $ recommendations. A recommendation is a segment of people like $ [l,r] $ which means the institute recommended that Malek gives one dollar to every person whose number is in this segment.

However this charity has very odd rules about the recommendations. Because of those rules the recommendations are given in such a way that for every two recommendation $ [a,b] $ and $ [c,d] $ one of the following conditions holds:

  • The two segments are completely disjoint. More formally either $ a<=b<c<=d $ or $ c<=d<a<=b $
  • One of the two segments are inside another. More formally either $ a<=c<=d<=b $ or $ c<=a<=b<=d $ .

The goodness of a charity is the value of maximum money a person has after Malek finishes giving his money. The institute knows for each recommendation what is the probability that Malek will accept it. They want to know the expected value of goodness of this charity. So they asked you for help.

You have been given the list of recommendations and for each recommendation the probability of it being accepted by Malek. You have also been given how much money each person initially has. You must find the expected value of goodness.

輸入格式

Malek is a rich man. He also is very generous. That's why he decided to split his money between poor people. A charity institute knows $ n $ poor people numbered from $ 1 $ to $ n $ . The institute gave Malek $ q $ recommendations. A recommendation is a segment of people like $ [l,r] $ which means the institute recommended that Malek gives one dollar to every person whose number is in this segment.

However this charity has very odd rules about the recommendations. Because of those rules the recommendations are given in such a way that for every two recommendation $ [a,b] $ and $ [c,d] $ one of the following conditions holds:

  • The two segments are completely disjoint. More formally either $ a<=b<c<=d $ or $ c<=d<a<=b $
  • One of the two segments are inside another. More formally either $ a<=c<=d<=b $ or $ c<=a<=b<=d $ .

The goodness of a charity is the value of maximum money a person has after Malek finishes giving his money. The institute knows for each recommendation what is the probability that Malek will accept it. They want to know the expected value of goodness of this charity. So they asked you for help.

You have been given the list of recommendations and for each recommendation the probability of it being accepted by Malek. You have also been given how much money each person initially has. You must find the expected value of goodness.

輸出格式

Output the sought value. Your answer will be considered correct if its absolute or relative error is less than $ 10^{-6} $ .


HDU5866

Problem Description

$ N $ lucky lancers are defending a tower. Each lancer occupies a position on the tower. Their leader, Lancer C¨2 Chulainn, a hero from the Ulster Cycle of Irish mythology, guards on the top of the tower. We can equalize the tower as a tree, each lancer has a supervisor. After several fierce battles, each lancer is wounded to a certain extent, the lancer with identity $ i $ took $ A_i $ arrows in his knee. This day, the lancers encounter the most powerful enemy ever -- Gilgamesh. Using his Noble Phantasm, Gate of Babylon, Gilgamesh launched $ M $ attacks altogether toward the poor lancers. Each time, Gilgamesh chooses a lancer and attacks. Due to the lancers' Protection from Arrows, this lancer has a probability to avoid this attack. However, if he failed to evade the attack, he and all his subordinates would take an arrow in the knee.

You are given the list of lancers Gilgamesh chose, we consider value $ S $ as the value of maximum number of arrows a lancers takes after Gilgamesh attacks.
Now C¨2 Chulainn would like to know the excepted value of $ S $.

Input

The input data will contain several cases(No more than \(5\)). Each case will begin with two numbers $ N $ and $ M $, the number of Lancers and the number of Gilgamesh's attacks. Each of the next $ n - 1 $ lines includes two numbers $ u $ and $ v $, meaning that $ v $ is $ u $'s supervisor.

The next line have N numbers , and the ith number indicates the number of arrows which the ith lancer have had initially in his knee.

The next M line means the attacks of Gilgamesh, include a integer v and a real number p, indicating the v-th lancer will be choose , and he has a probability p that he can't avoid this attack.

$ N \leq 100000, M \leq 3000 , A_i \leq 10000 , 0 \leq p \leq 1.0 , 1 \leq v \leq N $

Output

For each test case, print one line, the excepted value of \(S\). The answer is round to \(6\) decimal digits.

題意簡述

給出一個 \(N\) 個點的樹(樹以 \(1\) 為根),樹上的每一個點有點權。

接下來進行 \(M\) 次操作作用於 \(M\) 個不同的點,每次操作有 \(p_i\) 的機率使得這個點及子樹內所有點點權 \(+1\)

求樹上點權最大值的期望。


乍一看兩道題毫無相似之處。

但兩道題其實是雙倍經驗。

CF494C 中有這樣一句話:

保證區間不會交錯。

區間不交錯,意味著區間之間要麼包含要麼無交

要麼包含要麼無交,這就是在 $ \stackrel{{ \text{ 明擺著 }}} { \small \textsf{暗示} } $ 這是一個樹型結構。

包含的區間將被包含的區間視為父親,\(\Theta(n)\) (假如 \(\Theta(n)\) 排序)就能把樹建出來。

同樣的,用 dfs 序可以將 HDU5866 的子樹拍扁成區間。


HDU5866

首先要明確,期望的最大值不是最大值的期望。

如果直接算期望的話比較麻煩。

但眾所周知,期望就是機率乘上值,又因為值域很小,於是可以求出每種值出現的機率,然後推期望。

首先,沒用的點很多,只有被操作的 \(m\) 個點是真的有用的。

於是就可以用 \(m\) 個關鍵點和 \(1\) 建虛樹,每個點只保留其子樹的最大值 \(mx_i\)

void redfs(int p,int fa,int top){
    if(p!=1&&q[p].size()){
        v[top].push_back(p);
        top=p;
        dfn[p]=++tot;
    }
    mx[p]=a[p];
    for(auto to:u[p]){
        if(to==fa)continue;
        redfs(to,p,top);
        mx[p]=max(mx[p],mx[to]);
    }
}

接下來可以愉快的樹形 DP 求機率。

\(f_{i,j}\) 表示以 \(i\) 為根的子樹中,最大值為 \(j\) 的機率。

然而這樣開有點浪費,因為一個子樹的最大值至少為 \(mx_i\) (一次也不加),至多也就是 \(mx_i + m\) (\(m\) 次全加上)。

於是可以設 \(f_{i,j}\) 表示以 \(i\) 為根的子樹中,最大值比原來的最大值剛好\(j\) 的機率( \(j\) 可能為負 )。

這樣子能做,但還是有點麻煩,要拆貢獻。

於是我們再設 \(f_{i,j}\) 表示以 \(i\) 為根的子樹中,最大值比原來的最大值至多\(j\) 的機率( \(j\) 還是可能為負 )。

可以發現,我們本質上就是做了個字首和一樣的東西,現在的 \(f_{i,j} - f_{i,j-1}\)就是原來的 $ f_{i,j} $。

但這樣只需要將所有東西都加上就好了,沒什麼難推的地方。

設原來樹上的最大值為 \(MX\),以 \(i\) 為根的子樹構成的點集為 \(T_i\)

如果以 \(mx_i\) 一開始就大於 \(MX + j\) ,那麼 \(f_{i,j} = 0\)

否則,對於對點 \(i\) 的操作,設其發生機率為 \(p_i\),則有 \(p\) 的機率,原來最大值不大於 \(j-1\) ,加上 \(1\) 後不大於 \(j\),有 \(1 - p\) 的機率,原來最大值不大於 \(j\) ,現在還是不大於 \(j\)

由於我們先進優秀的狀態設計,中間的各種情況都被囊括其中了。

最終得到 $f_{i,j} = (1 - p_i) \times \prod_{k \in T_i}f_{k,j} + p \times f_{p_i,j-1} $。

void dfs(int p){
    for(int i=0;i<=m*2+1;i++){
        if(mx[p]<=mx[1]+i-m-1)f[dfn[p]][i]=1;
        else f[dfn[p]][i]=0;
    }
    for(auto to:v[p]){
        dfs(to);
        for(int i=0;i<=m*2+1;i++)
            f[dfn[p]][i]*=f[dfn[to]][i];
    }
    for(auto t:q[p]){
        for(int i=m*2+1;i;i--)
            f[dfn[p]][i]=(1-t)*f[dfn[p]][i]+t*f[dfn[p]][i-1];
        f[dfn[p]][0]=(1-t)*f[dfn[p]][0];
    }
}

code

懶得寫多測。

值得一提的是,將狀態最佳化後,狀態數量降到了 \(\Theta(m^2)\) 個,也就是說,值域可以開回 1e9 了(笑。

時間複雜度是 \(\Theta(n + m^2)\)

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
using db=double;
const int N=1e5+5,M=3005;
int n,m,a[N];
vector<int>v[N],u[N];
vector<db>q[N];
int dfn[N],tot,mx[N];
void redfs(int p,int fa,int top){
    if(p!=1&&q[p].size()){
        v[top].push_back(p);
        top=p;
        dfn[p]=++tot;
    }
    mx[p]=a[p];
    for(auto to:u[p]){
        if(to==fa)continue;
        redfs(to,p,top);
        mx[p]=max(mx[p],mx[to]);
    }
}
db f[M][M*2];
void dfs(int p){
    for(int i=0;i<=m*2+1;i++){
        if(mx[p]<=mx[1]+i-m-1)f[dfn[p]][i]=1;
        else f[dfn[p]][i]=0;
    }
    for(auto to:v[p]){
        dfs(to);
        for(int i=0;i<=m*2+1;i++)
            f[dfn[p]][i]*=f[dfn[to]][i];
    }
    for(auto t:q[p]){
        for(int i=m*2+1;i;i--)
            f[dfn[p]][i]=(1-t)*f[dfn[p]][i]+t*f[dfn[p]][i-1];
        f[dfn[p]][0]=(1-t)*f[dfn[p]][0];
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1,x,y;i<n;i++){
        cin>>x>>y;
        u[x].push_back(y),u[y].push_back(x);
    }
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++){
        int x;db p;
        cin>>x>>p;
        q[x].push_back(p);
    }
    tot=1,dfn[1]=1; 
    redfs(1,0,1);
    dfs(1);
    db ans=0;
    for(int i=m+1;i<=m*2+1;i++)
        ans+=(mx[1]+i-m-1)*(f[1][i]-f[1][i-1]);
    printf("%.3lf\n",ans);
    return 0;
}
//marisa

CF494C

如上所述,只要把區間轉成虛樹,這題就和 HDU5866 一模一樣了。

一個小區別就是隻有虛樹沒有原來的樹推不出來子樹最大值,所以要在建樹前求一下RMQ。

時間複雜度是 \(\Theta(n \log n + m^2)\),用一些 \(O(1)\) RMQ 之類的科技或許可以做到 \(\Theta(n + m^2)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
using db=double;
const int N=1e5+5,M=5005,logN=20;
int n,m,a[N];
struct node{int l,r;db p;}s[M];
inline bool cmp(node x,node y){
    if(x.l==y.l)return x.r>y.r;
    else return x.l<y.l;
}
int st[N][logN],lg[N];
inline int query(int l,int r){
    int k=lg[r-l+1];
    return max(st[l][k],st[r-(1<<k)+1][k]);
}
int mx[N];
vector<int> v[N];
db f[M][M*2];
void dfs(int p){
    for(int i=0;i<=m*2+1;i++){
        if(mx[p]<=mx[1]+i-m-1)f[p][i]=1;
        else f[p][i]=0;
    }
    for(auto to:v[p]){
        dfs(to);
        for(int i=0;i<=m*2+1;i++)
            f[p][i]*=f[to][i];
    }
    db t=s[p].p;
    for(int i=m*2+1;i;i--)
        f[p][i]=(1-t)*f[p][i]+t*f[p][i-1];
    f[p][0]=(1-t)*f[p][0];
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)st[i][0]=a[i];
    for(int j=1;j<logN;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]);
    for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
    for(int i=1;i<=m;i++)
        cin>>s[i].l>>s[i].r>>s[i].p;
    s[++m]=(node){1,n,0};
    sort(s+1,s+1+m,cmp);
    for(int i=1;i<=m;i++)
        mx[i]=query(s[i].l,s[i].r);
    for(int j=2;j<=m;j++)
        for(int i=j-1;i;i--)
            if(s[i].l<=s[j].l&&s[j].r<=s[i].r){
                v[i].push_back(j);
                break;
            }
    dfs(1);
    db ans=0;
    for(int i=m+1;i<=m*2+1;i++)
        ans+=(mx[1]+i-m-1)*(f[1][i]-f[1][i-1]);
    printf("%.9lf\n",ans);
    return 0;
}
//marisa

相關文章