UOJ #214 合唱队形 (概率期望计数、DP、Min-Max容斥)

9个月的心头大恨终于切掉了!!!!
非常好的一道题,不知为何uoj上被点了70个差评。

题目链接: http://uoj.ac/problem/214

题目大意: 请自行阅读。

题解:

官方题解讲得相当清楚,这里补充一下自己的一些理解。

首先来看\(O(2^{n-m}\times poly(n,m))\)的做法。

一种理解方式是官方题解。

设\(s\)为总共的课程个数(\(n\)个字符串的总长度),\(p(S)\)表示结尾位置为集合\(S\)的串全部匹配一共需要完成多少个不同的课程。设\(f(t)\)表示\(t\)时刻整个过程没有终止的概率,\(prob(S,t)\)表示\(S\)集合结尾的串在\(t\)时刻或者之前已经完成匹配的概率。\(ans\)为答案。

\(ans=\sum^{+\inf}_{t=0} f(t)=\sum^{+\inf}_{t=0} \sum_{S, |S|\ge 0}(-1)^{|S|} prob(S,t)=\sum^{+\inf}_{t=0} \sum_{S,|S|\ge 0}(-1)^{|S|} \sum^{f(S)}_{i=0} (-1)^i{f(S)\choose i}(\frac{s-i}{s})^t\\ =\sum^{+\inf}_{t=0} \sum_{S,|S|\ge 0}(-1)^{|S|} \sum^{f(S)}_{i=1} (-1)^i{f(S)\choose i}(\frac{s-i}{s})^t\\ =\sum_{S,|S|\ge 0}(-1)^{|S|} \sum^{f(S)}_{i=0} (-1)^i{f(s)\choose i}\sum^{\inf}_{t=0}(\frac{s-i}{s})^t=\sum_{S,|S|\ge 0}(-1)^{|S|} \sum^{f(S)}_{i=0} (-1)^i{f(S)\choose i} \frac{s}{i}\)

计算即可。

其实还有另外一种理解方式。我们要求的是所有串匹配时间最小值的期望,根据Min-Max容斥,我们可以通过枚举子集转化成对每个子集求该子集内串匹配时间最大值的期望,然后转化成每一时刻没结束的概率。

然后来看更难的\(O(2^m\times poly(n,m))\)做法。

对于所有\(p(S)\)相同的\(S\), 其对答案的贡献没有区别。

因此对于所有\(k\),考虑计算\(p(S)=k\)的\(S\)的个数。

然后直接dp即可。

代码错误记录: 注意solution1如果\(n-m=16\)的话数组要开到\(2^{17}\).

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define llong long long
using namespace std;

const int N = 30;
const int S = 26;
const int P = 998244353;
bool ok[N+3];
int f[N+3][S+3];
char str[N+3];
char a[N+3];
llong fact[2000003],finv[2000003],inv[2000003];
int cnt[(1<<17)+3];
int n,m,s;

llong quickpow(llong x,llong y)
{
    llong cur = x,ret = 1ll;
    for(int i=0; y; i++)
    {
        if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
        cur = cur*cur%P;
    }
    return ret;
}
llong comb(llong x,llong y) {return x<0 || y<0 || x<y ? 0ll : fact[x]*finv[y]%P*finv[x-y]%P;}

namespace Solution1
{
    int ff[N+3][S+3];
    llong ans;
    void solve()
    {
        ans = 0ll;
        for(int i=1; i<(1<<(n-m+1)); i++)
        {
            llong cur = 0ll; bool gg = false;
            for(int j=0; j<=n-m; j++)
            {
                if(i&(1<<j))
                {
                    for(int k=0; k<m; k++)
                    {
                        ff[j+k][a[k]-96] = true;
                        if(f[j+k][a[k]-96]==false) {gg = true; break;}
                    }
                    if(gg==true) break;
                }
            }
            if(gg)
            {
                for(int j=0; j<n; j++)
                {
                    for(int k=1; k<=S; k++)
                    {
                        ff[j][k] = false;
                    }
                }
                continue;
            }
            int num = 0;
            for(int j=0; j<n; j++)
            {
                for(int k=1; k<=S; k++)
                {
                    if(ff[j][k]==true) {num++;}
                }
            }
            for(int j=1; j<=num; j++)
            {
                llong tmp = (llong)s*inv[j]%P*comb(num,j)%P;
                if(!(j&1)) {cur = (cur+tmp)%P;}
                else {cur = (cur-tmp+P)%P;}
            }
            for(int j=0; j<n; j++)
            {
                for(int k=1; k<=S; k++)
                {
                    ff[j][k] = false;
                }
            }
            if(cnt[i]&1) {ans = (ans-cur+P)%P;}
            else {ans = (ans+cur+P)%P;}
        }
        printf("%lld\n",ans);
    }
}

namespace Solution2
{
    #define U ((1<<m)-1)
    const int M = 14;
    llong dp[N+3][(1<<M)+3][N*M+3];
    bool ff[N+3][M+3];
    void update(llong &x,llong y) {x = (x+y)%P;}
    void solve()
    {
        llong ans = 0ll;
        dp[m-1][0][0] = 1ll;
        for(int i=m-1; i<n; i++)
        {
            for(int j=0; j<(1<<m); j++)
            {
                bool okk = true;
                for(int k=0; k<m; k++) if((j&(1<<k)) && ok[i-k-1]==false) {okk = false; break;}
                if(!okk) continue;
                int p = 0;
                for(int k=0; k<m; k++)
                {
                    if(j&(1<<k))
                    {
                        for(int l=0; l<m; l++)
                        {
                            ff[i-k-1-m+1+l][a[l]-96] = true;
                        }
                    }
                }
                for(int l=0; l<m; l++)
                {
                    if(ff[i-m+1+l][a[l]-96]==false)
                    {
                        p++;
                    }
                }
                for(int k=0; k<m; k++)
                {
                    if(j&(1<<k))
                    {
                        for(int l=0; l<m; l++)
                        {
                            ff[i-k-1-m+1+l][a[l]-96] = false;
                        }
                    }
                }
                for(int k=0; k<=n*m; k++)
                {
                    if(dp[i][j][k]==0) continue;
                    update(dp[i+1][(j<<1)&U][k],dp[i][j][k]);
                    update(dp[i+1][(j<<1|1)&U][k+p],P-dp[i][j][k]);
                    dp[i][j][k] = 0ll;
                }
            }
        }
        for(int i=1; i<=n*m; i++)
        {
            llong coe = 0ll;
            for(int j=1; j<=i; j++)
            {
                llong tmp = s*inv[j]%P*comb(i,j)%P;
                if(j&1) {coe = (coe-tmp+P)%P;}
                else {coe = (coe+tmp)%P;}
            }
            llong sum = 0ll;
            for(int j=0; j<(1<<m); j++)
            {
                sum = (sum+dp[n][j][i])%P;
                dp[n][j][i] = 0ll;
            }
            ans = (ans+sum*coe)%P;
        }
        printf("%lld\n",ans);
    }
}

int main()
{
    fact[0] = 1ll; for(int i=1; i<=2000000; i++) fact[i] = fact[i-1]*i%P;
    finv[2000000] = quickpow(fact[2000000],P-2); for(int i=1999999; i>=0; i--) finv[i] = finv[i+1]*(i+1)%P;
    for(int i=1; i<=2000000; i++) inv[i] = finv[i]*fact[i-1]%P;
    cnt[0] = 0; for(int i=1; i<(1<<17); i++) cnt[i] = cnt[i>>1]+(i&1);
    int T; scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m); s = 0;
        for(int i=0; i<n; i++)
        {
            scanf("%s",str); int l = strlen(str);
            for(int j=0; j<l; j++)
            {
                f[i][str[j]-96] = true; s++;
            }
        }
        scanf("%s",a); bool exist = false;
        for(int i=m-1; i<n; i++)
        {
            ok[i] = true;
            for(int j=0; j<m; j++)
            {
                if(f[i-m+1+j][a[j]-96]==false) {ok[i] = false; break;}
            }
            if(ok[i]) {exist = true;}
        }
        if(exist==false) {printf("-1\n");}
        else
        {
            if(n-m<=16)
            {
                Solution1::solve();
            }
            else
            {
                Solution2::solve();
            }
        }
        for(int i=0; i<n; i++)
        {
            for(int j=1; j<=S; j++) f[i][j] = false;
            ok[i] = false;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/suncongbo/p/11027093.html

时间: 2024-08-29 12:54:12

UOJ #214 合唱队形 (概率期望计数、DP、Min-Max容斥)的相关文章

2019雅礼集训 D7T1 inverse [概率/期望,DP]

题目描述: 样例: input1: 3 1 1 2 3 output1: 833333340 input2: 5 10 2 4 1 3 5 output2: 62258360 数据范围与约定: 概率/期望的常用套路:将许许多多个元素单独考虑,以达到解决问题的目的. 这里发现不可能整个序列一起考虑,于是枚举任意两个位置,计算出k次翻转之后左边大于右边的概率,再加起来就好了. 于是我们有了一个非常暴力的DP: 令\(dp(i,j,k)?\) 表示k次翻转之后i位置大于j位置的概率.为了方便我们强行令

4.4 省选模拟赛 拉格朗日计数 树状数组+容斥.

像这种计数 问题什么的 是最讨厌的了... 考虑是环往环后面再续一段 暴力枚举前两个数字 树状数组统计第三个数的个数 n^2log. 考虑只枚举第个数 发现由于边界问题什么的很难处理. 再将枚举直接放到环上 发现边界问题没有了 不过存在 枚举第二个数之后 有 123 231 312 这三种形式. 第一种形式很好统计 预处理一下左边有多少个数字比自己小即可. 考虑第二种和第三种形式 很难在枚举2的时候统计出来这两种形式 考虑容斥 231=XX1-321. 312=3XX-321. 发现XX1和3X

uoj#279. 【UTR #2】题目交流通道(容斥+数数)

传送门 先考虑无解的情况,为以下几种:\(dis_{i,j}+dis_{j,k}<dis_{i,k}\),\(dis_{i,i}\neq 0\),\(dis_{i,j}\neq dis_{j,i}\),\(dis_{i,j}>K\).先大力特判掉 然后来考虑没有边权为\(0\)的时候,把原图中所有的边分类,对于\((i,j)\),如果存在\(k\)使得\(dis_{i,k}+dis_{k,j}=dis_{i,j}\),那么称其为\(B\)类边,否则为\(A\)类边.显然\(A\)类边的权值就是

【BZOJ-1419】Red is good 概率期望DP

1419: Red is good Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 660  Solved: 257[Submit][Status][Discuss] Description 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元.可以随时停止翻牌,在最优策略下平均能得到多少钱. Input 一行输入两个数R,B,其值在0到5000之间 Output 在最优策略下平均能得到多少钱

CF148D--Bag of mice+概率期望dp

第一道概率期望dp:) 其实和一般的dp也差不多,只要状态选好就行了. 定义dp[i][j]表示还剩i只白老鼠j只黑老鼠时候公主赢得概率. 则:1.公主选白老鼠,直接赢,概率:i/(i+j) 2.公主选黑老鼠 1)龙选黑老鼠,逃走黑老鼠:概率:j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2) 2)  龙选黑老鼠,逃走白老鼠:概率:j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2) 3) 龙选白老鼠,这样公主是必输的,不用考虑 然后dp[i][j]等于以上概率之和

LightOJ 1030 Discovering Gold (概率/期望DP)

题目链接:LightOJ - 1030 Description You are in a cave, a long cave! The cave can be represented by a \(1 \times N\) grid. Each cell of the cave can contain any amount of gold. Initially you are in position \(1\). Now each turn you throw a perfect \(6\) s

Codeforces - 1264C - Beautiful Mirrors with queries - 概率期望dp

一道挺难的概率期望dp,花了很长时间才学会div2的E怎么做,但这道题是另一种设法. https://codeforces.com/contest/1264/problem/C 要设为 \(dp_i\) 表示第 \(i\) 个格子期望经过多少次,所以 \(dp_{n+1}=1\). https://www.cnblogs.com/suncongbo/p/11996219.html 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12063633.ht

[华为]DP合唱队形

Description N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形. 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K).你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形. Inpu

【dp】合唱队形

例题二 合唱队形 (chorus.pas/c/cpp) 来源:NOIP2004(提高组) 第一题 N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形. 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,  则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K). 你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队