XSY2518记忆(memory)(状压dp,概率与期望,概率dp)

Posted ez-lcw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了XSY2518记忆(memory)(状压dp,概率与期望,概率dp)相关的知识,希望对你有一定的参考价值。

题面

Description

你在跟朋友玩一个记忆游戏。
朋友首先给你看了(n)个长度相同的串,然后从中等概率随机选择了一个串。
每一轮你可以询问一个位置上的正确字符,如果能够凭借已有的信息确定出朋友所选的串,那么游戏就结束了,你的成绩就是所用的轮数。
由于你实在太笨,不会任何策略,因此你采用一种方法,每次等概率随机询问一个未询问过的位置的字符。
现在你想知道,在这种情况下,你猜出结果所需的期望次数。

Input

(1)行包含一个整数 (n),表示串的个数。
(2sim n+1) 行每行包含一个长度相等的字符串,仅包含小写字母和大写字母。

Output

输出(1)行一个小数,表示猜出结果所需的期望次数,保留(10)位小数。

Sample Input

3
aaA
aBa
Caa

Sample Output

1.6666666667

HINT

设串长为(l)
对于(20\%)的数据,(n,l≤10)
对于(30\%)的数据, (n,l≤15)
对于(60\%)的数据, (n,l≤20)
对于(100\%)的数据, (n≤50)(l≤20)

题解

(w[i][j])表示每个字符串的第(i)位出现字符(j)的二进制状态。

例如对于样例:

3
aaA
aBa
Caa

(w[0]['a']=(011)_2),即所有字符串的第(0)位只有第(0)(1)个串出现字符('a'),所以(w[0]['a'])对应的二进制数的第(0)(1)位为(1)

这段的代码:

for(int j=0;j<len;j++)
{
    //s[i][j]即为第i个串的第j位
    w[j][s[i][j]]|=(1ll<<i);
}

(num[i])为当询问状态为(i)时,还不能确定这个串是不是朋友所选的串的串的个数,也可以理解为当询问状态为(i)时,还有多少个串满足条件。

询问状态即为用二进制存储的状压,询问状态(x)(x)为二进制数)的第(i)位若为(1),则说明已经询问过串的第(i)位。

(num[0])(n),即串的一位都没有询问,这时当然不能确定某个串是不是朋友所选的串,即为(n)

(b[i][j])表示选择第(i)个串,询问状态为(j)时的确定状态。(询问状态的定义同上)

确定状态即为用二进制存储的状压,确定状态(x)(x)为二进制数)的第(i)位若为(1),则说明还不确定第(i)个字符串不是朋友所选择的串,若为(0),则说明已经确定第(i)个字符串不是朋友所选择的串。

(b[i][0]=2^n-1),即询问状态为(0)时,这时当然不能确定某个串不是朋友所选的串,所以(b[i][0])的二进制表达式中应该第(0sim n-1)位都为(1),即(2^n-1)

然后我们枚举(i),再枚举(j)

我们设(now)(j)的二进制表达式的从低位到高位第一个出现的(1)的位数,再设(k)(joplus lowbit(j)),即把(j)的第(now)位由(1)改为(0),证明如下:

关于(joplus lowbit(j))为把(j)的第(now)位由(1)改为(0)的证明(不想看的可以跳过下面两个段落):

由于(lowbit(j))表示的是第(now)位为(1),第(0sim now-1)位为(0)的值,即(j)的二进制表达式中最低位的(1)所对应的值,例如:(lowbit((1001100)_2)=(100)_2)(lowbit((11110)_2)=(10)_2)

那么由(oplus)的运算法则(同0异1)可得(k=joplus lowbit(j))会除了把(j)的二进制表达式中第(now)位取反,即由(1)(0)之外,其余都不会变。即可得证。

那么从状态(k)转移到状态(j)即多询问了字符串的第(now)位。

(b[i][j]=b[i][k] & w[now][s[i][now]]),即把所有字符串中第(now)位不是(s[i][now])的在确定状态中设为(0),即确定所有字符串中第(now)位不是(s[i][now])的串不是朋友所选择的串,至于为何可以用这样的位运算维护,自己根据(&)(按位与)的运算法则(有0则0)手推一下吧。我才不说我是懒得写证明了呢

再判断一下,如果(b[i][j]!=lowbit(b[i][j])),即确定状态的二进制表达式中有不止一位有(1),那么说明在当前询问状态下,还是有大于(1)个串有可能是朋友选择的串,不能确定,所以(num[j]++),即对于选择第(i)个串,询问状态为(j)时,还是不能确定第(i)个串是不是朋友所选的串。揍一顿那个朋友不就好了

这一段的代码:

for(register int i=0;i<n;i++)
{
    b[i][0]=(1ll<<n)-1ll;//b[i][0]=2^n-1
    for(register int j=1;j<tot;j++)//tot为状态最大数,即1<<len
    {
        int k=j^lowbit(j);//把j的第now位由1改为0
        int now=__builtin_ctz(j);//now为j的二进制表达式的从低位到高位第一个出现的1的位数
        b[i][j]=b[i][k]&w[now][s[i][now]];
        if(b[i][j]!=lowbit(b[i][j])) num[j]++;//即确定状态的二进制表达式中有不止一位有1
    }
}

之后,我们设(sum[i])表示(i)的二进制表达式中(1)的个数。

(sum)就不多说了,(O(n))代码:

for(register int i=1;i<tot;i++)
    sum[i]=sum[i>>1]+(i&1);

接下来设(dp[i])表示转移到询问状态(i)的概率(他也可能不问某一位嘛,概率事件)

然后枚举询问状态(i),再枚举询问状态(i)在二进制表达式下的每一位。

如果这一位是(1),即有询问过这一位,我们才进行接下来的操作。这不废话吗

我们设(tmp=1/(len-sum[i]+1))为由询问状态(ioplus (1<<j))转移到询问状态(i)的概率。

那么就是(1)除以询问状态(ioplus (1<<j))(0)的个数(即还没询问的个数),即
(1/(len-sum[ioplus (1<<j)])),即(1/(len-sum[i]+1))

再让(tmp=f[ioplus (1<<j)]/(len-sum[i]+1)),即由无询问转移到询问状态为(i)的概率(且上一个询问状态为(ioplus(1<<j)))。

然后让(ans+=sum[i]*tmp*(num[ioplus (1<<j)]-num[i]))

(sum[i])(i)中有多少个(1),即询问次数。

(tmp)就是概率。

(num[ioplus (1<<j)]-num[i])就是计算从不确定变成确定的串的个数,那么我们选其中任意一个串作为答案都可以转移到询问状态(i)

合起来就是:(期望=操作次数/概率)

这一段的代码:

dp[0]=1;
for(int i=1;i<tot;i++)
{
    for(int j=0;j<len;j++)
    {
        if((i>>j)&1)
        {
            ld tmp=dp[i^(1<<j)]/(len-sum[i]+1);
            ans+=sum[i]*tmp*(num[i^(1<<j)]-num[i]);
            dp[i]+=tmp;//统计总概率
        }
    }
}

全部的代码:

#include<bits/stdc++.h>
 
#define ll long long
#define ld long double
 
using namespace std;
 
int n,s[55][25],len,tot,num[1<<20],sum[1<<20];
ll b[55][1<<20],w[25][55];
ld dp[1<<20],ans;
 
int change(char c)
{
    if('a'<=c&&c<='z')
        return c-'a';
    return c-'A'+26;
}
 
ll lowbit(ll x)
{
    return x&-x;
}
 
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        char ch[25];
        scanf("%s",ch);
        if(!len)len=strlen(ch),tot=1<<len;
        for(int j=0;j<len;j++)
        {
            s[i][j]=change(ch[j]);
            w[j][s[i][j]]|=(1ll<<i);
        }
    }   
    num[0]=n;
    for(int i=0;i<n;i++)
    {
        b[i][0]=(1ll<<n)-1ll;
        for(int j=1;j<tot;j++)
        {
            int k=j^lowbit(j);
            int now=__builtin_ctz(j);
            b[i][j]=b[i][k]&w[now][s[i][now]];
            if(b[i][j]!=lowbit(b[i][j])) num[j]++;
        }
    }
    for(int i=1;i<tot;i++)
        sum[i]=sum[i>>1]+(i&1);
    dp[0]=1;
    for(int i=1;i<tot;i++)
    {
        for(int j=0;j<len;j++)
        {
            if((i>>j)&1)
            {
                ld tmp=dp[i^(1<<j)]/(len-sum[i]+1);
                ans+=sum[i]*tmp*(num[i^(1<<j)]-num[i]);
                dp[i]+=tmp;
            }
        }
    }
    printf("%.10Lf
",ans/n);//因为选择每个串都是等概论事件,所以要除以一个n
    return 0;
}

以上是关于XSY2518记忆(memory)(状压dp,概率与期望,概率dp)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj1076 奖励关 状压dp 概率dp

lightoj 1011 Marriage Ceremonies (暴力)(记忆化dp,状压) 转

codeforces 482c 状压+概率DP

SCUT - 254 - 欧洲爆破 - 概率dp - 状压dp

HDU4336 Card Collector 概率DP求期望+状压

HDU 4336 Card Collector(状压 + 概率DP 期望)题解