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

题面

Description

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

Input

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

Output

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

Sample Input

3
aaA
aBa
Caa

Sample Output

1.6666666667

HINT

设串长为 l l l
对于 20 % 20\% 20%的数据, n , l ≤ 10 n,l≤10 n,l10
对于 30 % 30\% 30%的数据, n , l ≤ 15 n,l≤15 n,l15
对于 60 % 60\% 60%的数据, n , l ≤ 20 n,l≤20 n,l20
对于 100 % 100\% 100%的数据, n ≤ 50 n≤50 n50 l ≤ 20 l≤20 l20

题解

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

例如对于样例:

3
aaA
aBa
Caa

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

这段的代码:

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

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

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

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

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

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

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

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

我们设 n o w now now j j j的二进制表达式的从低位到高位第一个出现的 1 1 1的位数,再设 k k k j ⊕ l o w b i t ( j ) j\oplus lowbit(j) jlowbit(j),即把 j j j的第 n o w now now位由 1 1 1改为 0 0 0,证明如下:

关于 j ⊕ l o w b i t ( j ) j\oplus lowbit(j) jlowbit(j)为把 j j j的第 n o w now now位由 1 1 1改为 0 0 0的证明(不想看的可以跳过下面两个段落):

由于 l o w b i t ( j ) lowbit(j) lowbit(j)表示的是第 n o w now now位为 1 1 1,第 0 ∼ n o w − 1 0\sim now-1 0now1位为 0 0 0的值,即 j j j的二进制表达式中最低位的 1 1 1所对应的值,例如: l o w b i t ( ( 1001100 ) 2 ) = ( 100 ) 2 lowbit((1001100)_2)=(100)_2 lowbit((1001100)2)=(100)2 l o w b i t ( ( 11110 ) 2 ) = ( 10 ) 2 lowbit((11110)_2)=(10)_2 lowbit((11110)2)=(10)2

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

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

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

再判断一下,如果 b [ i ] [ j ] ! = l o w b i t ( b [ i ] [ j ] ) b[i][j]!=lowbit(b[i][j]) b[i][j]!=lowbit(b[i][j]),即确定状态的二进制表达式中有不止一位有 1 1 1,那么说明在当前询问状态下,还是有大于 1 1 1个串有可能是朋友选择的串,不能确定,所以 n u m [ j ] + + num[j]++ num[j]++,即对于选择第 i i i个串,询问状态为 j j j时,还是不能确定第 i i 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
    }
}

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

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

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

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

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

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

我们设 t m p = 1 / ( l e n − s u m [ i ] + 1 ) tmp=1/(len-sum[i]+1) tmp=1/(lensum[i]+1)为由询问状态 i ⊕ ( 1 < < j ) i\oplus (1<<j) i(1<<j)转移到询问状态 i i i的概率。

那么就是 1 1 1除以询问状态 i ⊕ ( 1 < < j ) i\oplus (1<<j) i(1<<j) 0 0 0的个数(即还没询问的个数),即
1 / ( l e n − s u m [ i ⊕ ( 1 < < j ) ] ) 1/(len-sum[i\oplus (1<<j)]) 1/(lensum[i(1<<j)]),即 1 / ( l e n − s u m [ i ] + 1 ) 1/(len-sum[i]+1) 1/(lensum[i]+1)

再让 t m p = f [ i ⊕ ( 1 < < j ) ] / ( l e n − s u m [ i ] + 1 ) tmp=f[i\oplus (1<<j)]/(len-sum[i]+1) tmp=f[i(1<<j)]/(lensum[i]+1),即由无询问转移到询问状态为 i i i的概率(且上一个询问状态为 i ⊕ ( 1 < < j ) i\oplus(1<<j) i(1<<j))。

然后让 a n s + = s u m [ i ] ∗ t m p ∗ ( n u m [ i ⊕ ( 1 < < j ) ] − n u m [ i ] ) ans+=sum[i]*tmp*(num[i\oplus (1<<j)]-num[i]) ans+=sum[i]tmp(num[i(1<<j)]num[i])

s u m [ i ] sum[i] sum[i] i i i中有多少个 1 1 1,即询问次数。

t m p tmp tmp就是概率。

n u m [ i ⊕ ( 1 < < j ) ] − n u m [ i ] num[i\oplus (1<<j)]-num[i] num[i(1<<j)]num[i]就是计算从不确定变成确定的串的个数,那么我们选其中任意一个串作为答案都可以转移到询问状态 i 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\n",ans/n);//因为选择每个串都是等概论事件,所以要除以一个n
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值