1. 程式人生 > >2018年8月17日暑假訓練日記

2018年8月17日暑假訓練日記

昨天的那個期望題的正解:

相當於每個點作為起點,列舉其終點所獲得的期望之和。

可以這樣理解,列舉起點的時候,可以發現:

...011...110...一個這樣的字串,只需要dp獲得011...110這個串的概率,乘以這一段的取值,就是這一段貢獻的期望,因為就相當於其他的點為任意情況而不考慮其貢獻得到的期望,由於是對每個點作為起點並列舉其終點,因此不會造成重複,合理利用了期望的性質

#include<iostream>

#include<cstring>

#include<cmath>

#include<cstdio>

#include<algorithm>

#define mo 1000000007

using namespace std;

long long p[1010];

long long ip[1010];

long long a[1010];

long long dp[1010][1010];

long long qpow(long long x,long long y){

    if (x==0)return 0;

    long long ans=1;

    long long k=x;

    while(y){

        if(y&1)ans=ans*k%mo;

        k=k*k%mo;

        y>>=1;

    }

    return (ans+mo)%mo;

}

void get(long long m){

    long long i,j;

    for (i=1;i<=1000;i++){

        a[i]=qpow(i,m);

    }

}

int main(){

    long long n,m;

    long long i,j,k;

    while (scanf("%lld%lld",&n,&m)!=EOF){

        get(m);

        long long temp=qpow(100,mo-2);

        for (i=1;i<=n;i++){

            scanf("%lld",&p[i]);

            ip[i]=(100-p[i])*temp%mo;

            p[i]=p[i]*temp%mo;

        }

        p[0]=p[n+1]=0;

        ip[0]=ip[n+1]=100*temp%mo;

        long long ans=0;

        for (i=1;i<=n;i++){

            dp[i][i-1]=1;

            for (j=i;j<=n;j++){

                dp[i][j]=dp[i][j-1]*p[j]%mo;

                ans=(ans+dp[i][j]*ip[i-1]%mo*ip[j+1]%mo*a[j-i+1]%mo+mo)%mo;

            }

        }

        printf("%lld\n",ans);

    }

}

而用期望dp的方法解釋就是:

設前面串的1的連續長度的期望為x,則得到了下一位的期望為:(x+1)^m-x^m

可以用二項展開列舉他每一次方的期望,然後合併得到。

【BZOJ4318】OSU!以這個題目改造的題目,我得到了m次方的通解為:

#include<iostream>

#include<cstring>

#include<cmath>

#include<cstdio>

#include<algorithm>

#define mo 1000000007

using namespace std;

long long a[1010];

long long b[1010];

double p[100010];

double l[100010][10];

double ans[100010];

long long qpow(long long a,long long b){

    long long ans=1;

    long long k=a;

    while(b){

        if(b&1)ans=ans*k%mo;

        k=k*k%mo;

        b>>=1;

    }

    return (ans+mo)%mo;

}

void get(){

    long long i;

    a[0]=1;

    b[0]=1;

    a[1]=1;

    b[1]=1;

for (i=2;i<1010;i++){

        a[i]=((a[i-1]*i)%mo+mo)%mo;

b[i]=(qpow(a[i],mo-2)+mo)%mo;

}

}

long long zuhe(long long n,long long m){

    if (n<m||m<0) return 0;

    return  a[n]*b[m]%mo*b[n-m]%mo;

}

int main(){

    long long n,m;

    long long i,j,k;

    get();

    while (scanf("%lld",&n)!=EOF){

        m=3;

        long long temp=qpow(100,mo-2);

        memset (l,0,sizeof(l));

        memset (ans,0,sizeof(ans));

        for (i=1;i<=n;i++){

            scanf("%lf",&p[i]);

            //p[i]=p[i]*temp%mo;

            for (j=1;j<=m-1;j++){

                l[i][j]=(l[i-1][j]+1);

                for (k=1;k<=j-1;k++){

                    l[i][j]=(l[i][j]+zuhe(j,k)*l[i-1][k]);

                }

                l[i][j]=l[i][j]*p[i];

            }

            double temp=1.0;

            for (j=1;j<=m-1;j++){

                temp=temp+zuhe(m,j)*l[i-1][j];

            }

            ans[i]=ans[i-1]+p[i]*temp;

        }

        printf("%lf\n",ans[n]);

    }

}

/*

3

0.5

0.5

0.5

*/

這裡可以根據需要調整m,但是昨天的題目正好卡掉了這個n*(m+1)*m/2的演算法,要是m是500就可以很簡單的通過,但是對於這個題,可以通過維護一個組合數的方式來解決,但是上面的方法更好理解,可惜超時,這裡說的可以用第二類斯特林數進行轉換,但是並不理解為何,斯特林數可以表示x的冪次,但是我們要對期望做運算。題解也沒有做出更加深刻的解釋。

然後看了一下可持久化線段樹的寫法,以前一直知道有怎麼個東西, 也知道是幹什麼的,但是一直沒有學習,今天拓展kmp看的有點迷,就看了一下可持久化線段樹。

當然,線段樹還是log2進行修改和查詢,但是這裡要求記錄過程,就是對上面某一位置的某個數字進行更改或者查詢操作,當然,使用n個線段樹肯定是mle+tle。這裡,其實並不難理解,以前根樹以及左右孩子的標號是固定的,這裡用陣列替代。若需要修改某一次的操作下的值,則新開log2n的空間來維持這段操作,建樹方法相同,但是用陣列的話就可以變更樹的方向,並不難學習的操作,但是還沒有發現好的模板程式碼。