概率DP(洛谷/CF题目)

P3802 小魔女帕琪(简单排列组合)

所有的排列情况为 ( S = ∑ a i ) ! (S=\sum{a_i})! (S=ai)!。因此接下来考虑可以形成一个大招的情况数量即可。
大招是1234567的排列。即为 7 ! 7! 7!,每个数字 i i i a i a_i ai个选择,为 ∏ i = 1 7 a i \prod_{i = 1}^{7}{a_i} i=17ai,这个大招的结束位置可以在 S − 6 S-6 S6个位置,即为 C S − 6 1 = s − 6 C_{S-6}^1 = s-6 CS61=s6,最后剩下的位置要咋放就咋放,因此是 ( S − 7 ) ! (S-7)! (S7)!,因此所有排列产生的大招一共有 7 ! ∏ i = 1 7 a i C S − 6 1 ( S − 7 ) ! 7!\prod_{i = 1}^{7}{a_i}C_{S-6}^{1}(S-7)! 7!i=17aiCS61(S7)!个,最后除以 S ! S! S!即可。

int a[7], s = 0;
int main()
{
    double ans = 1;
    for(int i = 0; i < 7; i ++) 
        cin >> a[i], s += a[i], ans *= a[i] * (1 + i);
    if(!ans) printf("0.000\n");
    else
    {
        for(int i = 0; i < 6; i ++) ans /= s - i;
        printf("%.3f\n", ans);
    }
    return 0;
}

P1365 WJMZBMR打osu! / Easy(简单期望)

期望题先判断题目是否具有方向性,如果有方向,一般从终止状态向起始状态推。
此题没有方向性,从前到后和从后到前没有什么本质的区别,因此直接从前向后推导即可。
f [ i ] f[i] f[i]表示从1到 i i i的分数期望,推导到 f [ i ] f[i] f[i],可以将 f [ i − 1 ] f[i-1] f[i1]的期望当作一个已知值,即直接理解成从1到 i i i的分数就是 f [ i − 1 ] f[i-1] f[i1]。到 i i i的时候分3种情况:

  1. s t r [ i ] = str[i]= str[i]=‘o’,此时改变的是后面连续’o’的长度,我们只能确定最后一个是’o’,并不知道前面的连续长度,因此还需要设另一个变量 l e n [ i ] len[i] len[i],表示到 f [ i ] f[i] f[i]时末尾’o’的连续长度期望。因此可转移: l e n [ i ] = l e n [ i − 1 ] + 1 , f [ i ] = f [ i − 1 ] − ( l e n [ i − 1 ] ∗ l e n [ i − 1 ] ) 2 + ( l e n [ i ] ∗ l e n [ i ] ) 2 len[i]=len[i-1]+1, f[i]=f[i-1]-(len[i-1]*len[i-1])^2+(len[i]*len[i])^2 len[i]=len[i1]+1,f[i]=f[i1](len[i1]len[i1])2+(len[i]len[i])2 简化一下得到 l e n [ i ] = l e n [ i − 1 ] + 1 , [ i ] = f [ i − 1 ] + 2 ∗ l e n [ i − 1 ] + 1 len[i]=len[i-1]+1 ,[i]=f[i - 1]+2*len[i-1]+1 len[i]=len[i1]+1,[i]=f[i1]+2len[i1]+1
  2. s t r [ i ] = str[i]= str[i]='x’并不会对答案有贡献: l e n [ i ] = 0 , f [ i ] = f [ i − 1 ] len[i]=0, f[i]=f[i-1] len[i]=0,f[i]=f[i1]
  3. s t r [ i ] ] str[i]] str[i]]‘?’,为’o’和为’x’的概率相同,因此直接是1,2两种情况结合。
const int N = 3e5 + 10;
int n;
long double f, len;
char s[N];
int main()
{
    scanf("%d", &n);
    scanf("%s", s + 1);
    for(int i = 1; i <= n; i ++)
    {
        if(s[i] == 'o') f += 2 * len + 1, len ++;
        else if(s[i] == 'x') len = 0;
        else f += (2 * len + 1) / 2, len = (len + 1) / 2;
    }
    printf("%.4Lf", f);
    return 0;
}

P2634 [国家集训队]聪聪可可(树上期望)

n n n个点,聪聪任取1个点,即 n n n种情况,可可任取1个点,即 n n n种情况,根据乘法原理,最终一共有 n 2 n^2 n2条路径。最后要求和为3的倍数的路径条数。很明显要是3的倍数,所以所有边权都直接%3,最后树上权值和也%3,将和都控制在0,1,2,更容易表示,转移和存储。
因为是树的题,所以应该利用树形结构来计算路径条数,或者可以说,是将路径按照和为0,1,2分成3类。

对于某个点 u u u,只考虑【仅经过 u u u与子树组成的路径】,而不考虑其他的路径,对于每个节点都这样考虑,从而划分成小问题。

现在思考:【仅经过 u u u与子树组成且路径和为0】的路径有多少条?
{ 最暴力的枚举思路为:设 u u u的子树大小为 s z sz sz,那么它到每个 s z sz sz个点一共有 s z sz sz条路径,枚举这 s z sz sz条路径,记作 i i i,再枚举剩下的 s z − 1 sz-1 sz1条路径,记作 j j j,记录将路径 i , j i,j i,j合并起来的新路径和为0的条数。但是这样枚举并不正确,因为 i i i j j j两条路径可能会经过同一条边。并且这种枚举的复杂度太高,为 m 2 m^2 m2。因此得解决经过同一条边的问题,并且降低复杂度。}

在这里插入图片描述
降低复杂度很容易想到可以将以 u u u为一端,以 u u u的子树为另一端的路径和为0,1,2的数量进行计算。只需要枚举 u u u的每条字数边, O ( m ) O(m) O(m)即可算出。为了解决不同路径经过同一条子树边的问题,可以在统计 0 , 1 , 2 0,1,2 0,1,2路径和的数量时,在将 u u u的当前子节点 v v v,相应边为 w w w,进行统计之前,先【计算从第1个子树点到第 v − 1 v-1 v1个子树点】与【第v个子树点】的路径进行结合的新路径和为0的路径条数,因为还未统计【以 u u u为一段,以 v v v的子树节点为另一端的路径和为0,1,2】的数量,因此不会统计到经过同一条 u u u的子树边的情况。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
struct E{int to, w, nxt;}e[N << 1];
int head[N], cnt;
void add(int u, int v, int w)
{
    e[++ cnt] = {v, w, head[u]};
    head[u] = cnt;
}
int gcd(int a, int b) {return !b ? a : gcd(b, a % b);}
int n;
 //f[i][j]表示以i为根的子树,以i为路径一端,以i的子树为路径另一端的,和为j的路径数量的前缀和
int f[N][3];
int sub(int a, int b){return (a - b + 3) % 3;}
int ans;
void dfs(int u, int fa)
{
    f[u][0] = 1;
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to; if(v == fa) continue;
        dfs(v, u);
        for(int j = 0; j < 3; j ++)
            ans += f[u][sub(sub(0, j), e[i].w)] * f[v][j] * 2;
        for(int j = 0; j < 3; j ++)
            f[u][j] += f[v][sub(j, e[i].w)];
    }
}
int main()
{
    scanf("%d\n", &n);
    for(int i = 1; i < n; i ++)
    {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        add(u, v, w % 3), add(v, u, w % 3);
    }
    dfs(1, 0);
    ans += n;
    int tmp = gcd(ans, n * n);
    printf("%d/%d\n", ans / tmp, n * n / tmp);
    return 0;
}

CF804D Expected diameter of a tree(树的直径+期望)

#include<bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
struct E{int to, w, nxt;}e[N << 1];
int head[N], cnt;
void add(int u, int v, int w)
{
    e[++ cnt] = {v, w, head[u]};
    head[u] = cnt;
}
int gcd(int a, int b) {return !b ? a : gcd(b, a % b);}
int n;
 //f[i][j]表示以i为根的子树,以i为路径一端,以i的子树为路径另一端的,和为j的路径数量的前缀和
int f[N][3];
int sub(int a, int b){return (a - b + 3) % 3;}
int ans;
void dfs(int u, int fa)
{
    f[u][0] = 1;
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to; if(v == fa) continue;
        dfs(v, u);
        for(int j = 0; j < 3; j ++)
            ans += f[u][sub(sub(0, j), e[i].w)] * f[v][j] * 2;
        for(int j = 0; j < 3; j ++)
            f[u][j] += f[v][sub(j, e[i].w)];
    }
}
int main()
{
    scanf("%d\n", &n);
    for(int i = 1; i < n; i ++)
    {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        add(u, v, w % 3), add(v, u, w % 3);
    }
    dfs(1, 0);
    ans += n;
    int tmp = gcd(ans, n * n);
    printf("%d/%d\n", ans / tmp, n * n / tmp);
    return 0;
}

CF16E Fish(简单状压+概率)

double f[1 << 20];
double a[20][20];
int n;
int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++) scanf("%lf", &a[i][j]);
    int mx = (1 << n) - 1;
    f[mx] = 1;
    for(int i = mx; i; i --)
    {
        bitset<20> b(i); int cnt = b.count();
        for(int j = 0; j < n; j ++)
        {
            if((i >> j) & 1) continue;
            int pre = i + (1 << j);
            for(int k = 0; k < n; k ++)
            {
                if(((i >> k) & 1) == 0) continue;
                f[i] += f[pre] * a[k][j] * 2 / ((cnt + 1) * cnt);
            }
        }
    }
    for(int i = 0; i < n; i ++) printf("%.6f ", f[1 << i]);
    return 0;
}

P4562 [JXOI2018]游戏(质因数分解+组合数)

每个排列都有一个最小值t,求所有排列的t的和。很明显排列有 n ! n! n!,而t最多只有n种,因此可以看对于每个t对多少个排列有贡献。
对于每个排列,其中有一些必须选的数,这些数在l~r中没有将其作为倍数的数,设一共有s个,因此每个排列至少选s个。
从s到n枚举t的大小,表示检查到第i个所有办公室都认真工作了。
答案为 ∑ i = s n i ∗ f [ i ] \sum^{n}_{i=s}{i*f[i]} i=snif[i]
接下来计算f[i],f[i]表示t=i的排列数量。因此第i个必然是s个必选的一个,否则在前 t − 1 t-1 t1个已经包含了s个必选,第t个位置不是必选,这个位置的非必选数也可以不选。前 i i i确定要s个必选数,剩下 i − s i-s is个,在 n − s n-s ns个选,因此有 C ( n − s , i − s ) C(n-s,i-s) C(ns,is),前i-1个位置的数排列为 ( i − 1 ) ! (i-1)! (i1)!,后n-i个数的排列为 n − i n-i ni,因此 f [ i ] = s ∗ C ( n − s , i − s ) ∗ ( i − 1 ) ! ∗ ( n − i ) ! f[i]=s*C(n-s,i-s)*(i-1)!*(n-i)! f[i]=sC(ns,is)(i1)!(ni)!

const int N = 1e7 + 10, mod = 1e9 + 7;
bool vis[N];
int p[N]; //p[0]表示素数的个数 vis表示此数字是否非素数
int mxf[N];
void gp()
{
    for(int i = 1; i < N; i ++)
    {
        if(!vis[i]) p[++ p[0]] = i;
        for(int j = 2; j <= p[0] && 1ll * p[j] * i < N; j ++)
        {
            vis[p[j] * i] = true;
            mxf[p[j] * i] = i;
            if(i % p[j] == 0) break;
        }
    }
}
int qpow(int a, int b)
{
    int ans = 1;
    while(b)
    {
        if(b & 1) ans = 1ll * ans * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return ans;
}
int fact[N], inv[N];
int C(int n, int m)
{
    return 1ll * fact[n] * inv[n - m] % mod * inv[m] % mod;
}
void pre()
{
    fact[0] = 1;
    for(int i = 1; i < N; i ++) fact[i] = 1ll * fact[i - 1] * i % mod;
    inv[N - 1] = qpow(fact[N - 1], mod - 2);
    for(int i = N - 2; i >= 0; i --) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
int main()
{
    int l, r; cin >> l >> r;
    int s = r - l + 1, n = s;
    gp();
    pre();
    if(l == 1) s = 1;
    else
        for(int i = l; i <= r; i ++)
        {
            //最大因数是否在l-r之间
            if(vis[i] && mxf[i] >= l) s --;
        }
    int ans = 0;
    for(int i = s; i <= n; i ++)
        ans = (ans + 1ll * s * C(n - s, i - s) % mod * fact[i] % mod * fact[n - i] % mod) % mod;
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值