概率期望dp

Blocks
期望dp,从已经满足的点倒着推,首先考虑状态,发现 n n n很小,直接状压,然后暴力枚举状态看是否全部覆盖,发现坐标跨度很大,对坐标离散化,依次差分修改, O ( n 2 2 n ) O(n^22^n) O(n22n),然后就可以直接dp了
d p i = ∑ j d p i [ ( 1 < < j ) & i ] + 1 + ∑ j d p i ∣ j + 1 [ ( 1 < < j ) & 1 = = 0 ] n dp_i={{\sum_j dp_i [(1<dpi=njdpi[(1<<j)&i]+1+jdpij+1[(1<<j)&1==0]
化简得: d p i = n + ∑ j d p i ∣ j [ ( 1 < < j ) & 1 = = 0 ] n − c n t 1 ( i ) dp_i={ n+\sum_j dp_{i|j} [(1<dpi=ncnt1(i)n+jdpij[(1<<j)&1==0]

#include 
#define ll long long
const ll mod=998244353;
int x1[15],x2[15],y2[15];
int sum[50][50];
ll qpow(ll x,ll p){
    ll ans=1;
    x%=mod;
    while (p){
        if (p&1) ans=ans*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return ans;
}

ll ni(ll x){
    return qpow(x,mod-2);
}
void solve(){
    int n,w,h;
    std::cin>>n>>w>>h;
    std::vector<int> y1(n);
    std::vector<int> temp;
    for (int i=0;i<n;i++){
        std::cin>>x1[i]>>y1[i]>>x2[i]>>y2[i];
        temp.push_back(x1[i]);
        temp.push_back(x2[i]);
        temp.push_back(y1[i]);
        temp.push_back(y2[i]);
    }
    temp.push_back(0);
    temp.push_back(w);
    temp.push_back(h);
    std::sort(temp.begin(),temp.end());

    int cnt=0;
    std::map<int,int> mp;
    for (int i=0;i<temp.size();i++){
        if (i==0||temp[i]!=temp[i-1]){
            mp[temp[i]]=++cnt;
        }
    }
    w=2*mp[w]-1;
    h=2*mp[h]-1;
    for (int i=0;i<n;i++){
        x1[i]=std::min(w,2*mp[x1[i]]-1);
        x2[i]=std::min(w,2*mp[x2[i]]-1);
        y1[i]=std::min(h,2*mp[y1[i]]-1);
        y2[i]=std::min(h,2*mp[y2[i]]-1);
    }

    auto f=[&](int x){
        memset(sum,0,sizeof(sum));
        for (int i=0;i<n;i++){
            if (!((1<<i)&x)) continue;
            sum[x1[i]][y1[i]]++;
            sum[x1[i]][y2[i]+1]--;
            sum[x2[i]+1][y1[i]]--;
            sum[x2[i]+1][y2[i]+1]++;
        }
        for (int i=1;i<=w;i++){
            for (int j=1;j<=h;j++){
                sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
                if (!sum[i][j]) return -1;
            }
        }
        return 0;
    };
    std::vector<ll> dp(1<<n);
    for (int i=(1<<n)-1;i>=0;i--){
        dp[i]=f(i);
        if (i==(1<<n)-1&&dp[i]==-1){
            std::cout<<"-1\n";
            return;
        }
    }

    for (int i=(1<<n)-1;i>=0;i--){
        if (dp[i]==0) continue;
        dp[i]=0;
        int cnt=0;
        for (int j=0;j<n;j++){
            if ((1<<j)&i){
                cnt++;
            }else{
                dp[i]=(dp[i]+dp[i|(1<<j)])%mod;
            }
        }
        dp[i]=(dp[i]+n)%mod;
        dp[i]=dp[i]*ni(n-cnt)%mod;
    }
    std::cout<<dp[0]<<"\n";
}

P3239 [HNOI2015] 亚瑟王
一张卡牌一整局只会产生一次贡献,不妨直接考求出这一次贡献所产生的的概率为多少。

g i g_i gi表示第 i i i张牌在 r r r轮中发动一次技能的概率,发现这个东西不太好找前后的递推关系。我们发现当前卡牌是否发动技能的概率受到前面卡牌的影响(前面选了几张卡,那么我再怎么也要有几轮把前面的卡选完),但不受到后面卡牌的影响,所以我们设 f i , j f_{i,j} fi,j,表示在这 r r r轮中,前 i i i张牌有 j j j张牌被选的概率,这样我们就知道有多少轮能在 i i i这个地方做出决策(产生被选的概率),从而算出 g i g_i gi

然后 f i , j f_{i,j} fi,j每次只要看在 r r r轮中 i i i这张卡牌是否产生发动一次技能,分类转移

double ksm(double x,int p){
    double ans=1;
    while (p){
        if (p&1) ans=ans*x;
        x*=x;
        p>>=1;
    }
    return ans;
}
void solve(){
    int n,r;
    std::cin>>n>>r;
    std::vector<double> p(n+1),d(n+1);
    for (int i=1;i<=n;i++){
        std::cin>>p[i]>>d[i];
    }

    std::vector<std::vector<double>> f(n+1,std::vector<double>(r+1));
    f[0][0]=1;
    for (int i=1;i<=n;i++){
        for (int j=0;j<=r;j++){
            f[i][j]=f[i-1][j]*ksm(1-p[i],r-j);
            if (j) f[i][j]+=f[i-1][j-1]*(1-ksm(1-p[i],r-j+1));
        }
    }

    std::vector<double> g(n+1);
    double ans=0;
    for (int i=1;i<=n;i++){
        for (int j=0;j<=r;j++){
            g[i]+=f[i-1][j]*(1-ksm(1-p[i],r-j));
        }
        ans+=g[i]*d[i];
    }
    std::cout<<std::fixed<<std::setprecision(10)<<ans<<"\n";
}

P3750 [六省联考 2017] 分手是祝愿
50%的数据 k = = n k==n k==n,也就是说只要找到最小的关灯次数
直接从后往前遍历,当前点为 i i i,如果现在 i i i这个位置为1,那只能在这按一次开关,然后 O ( n ) O(\sqrt n) O(n )去修改其它位置的状态。

考虑有等概率随机按开关的情况,其实在前面我们就能知道,给出的数据我们一共要按的开关就只有那几个,一个开关重复按两次是没有效果的,按错了开关到最后也得按回去,所以我们状态设计就是已经按了几个要按的开关, f i f_i fi表示已经按了 i i i个要按的开关的期望。
i < = k , f i = k i<=k,f_i=k i<=k,fi=k
i > k i>k i>k 我们分类按对了开关或按错了开关:
f i = i n ⋅ f i − 1 + n − i n ⋅ f i + 1 f_i={i \over n}\cdot f_{i-1}+{n-i \over n} \cdot f_{i+1} fi=nifi1+nnifi+1
f i = f i − 1 + b i f_i=f_{i-1}+b_i fi=fi1+bi b i b_i bi代入式子中解出 b i b_i bi的递推式,然后根据推出 f i f_i fi

另一种状态设计就是 f i f_i fi表示剩下 i i i个开关要按,到剩下 i − 1 i-1 i1个开关要按的期望为多少
f i = i n + n − i n ( 1 + f i + 1 + f i ) f_i={i \over n}+{n-i \over n}(1+f_{i+1}+f_{i}) fi=ni+nni(1+fi+1+fi) 然后移项即可得到递推式
最后累加

代码是第一种方法

#include 
#define ll long long
const ll mod=100003;
ll ksm(ll x,ll p){
    ll ans=1;
    x%=mod;
    while (p){
        if (p&1) ans=ans*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return ans;
}
ll ni(ll x){
    return ksm(x,mod-2);
}
void solve(){
    int n,k;
    std::cin>>n>>k;
    std::vector<int> a(n+1);
    for (int i=1;i<=n;i++){
        std::cin>>a[i];
    }

    std::vector<ll> b(n+1);
    b[n]=1;
    int cnt=0;
    for (int i=n;i>=1;i--){
        if (a[i]){
            cnt++;
            for (int j=1;j*j<=i;j++){
                if (i%j==0){
                    a[j]^=1;
                    if (i/j!=j) a[i/j]^=1;
                }
            }
        }

        if (i<n){
            b[i]=(n-i+mod)%mod*b[i+1]%mod*ni(i)%mod+n*ni(i)%mod;
            b[i]%=mod;
        }
    }
    std::vector<ll> f(n+1);
    for (int i=1;i<=k;i++){
        f[i]=i%mod;
    }
    for (int i=k+1;i<=cnt;i++){
        f[i]=(f[i-1]+b[i])%mod;
    }
    ll ans=f[cnt];
    for (int i=1;i<=n;i++){
        ans=ans*i%mod;
    }
    std::cout<<ans;
}

你可能感兴趣的:(dp,算法,动态规划)