题面
Description
你在跟朋友玩一个记忆游戏。
朋友首先给你看了\(n\)个长度相同的串,然后从中等概率随机选择了一个串。
每一轮你可以询问一个位置上的正确字符,如果能够凭借已有的信息确定出朋友所选的串,那么游戏就结束了,你的成绩就是所用的轮数。
由于你实在太笨,不会任何策略,因此你采用一种方法,每次等概率随机询问一个未询问过的位置的字符。
现在你想知道,在这种情况下,你猜出结果所需的期望次数。
Input
第\(1\)行包含一个整数 \(n\),表示串的个数。
第 \(2\sim 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]\)的二进制表达式中应该第\(0\sim n-1\)位都为\(1\),即\(2^n-1\)。
然后我们枚举\(i\),再枚举\(j\):
我们设\(now\)为\(j\)的二进制表达式的从低位到高位第一个出现的\(1\)的位数,再设\(k\)为\(j\oplus lowbit(j)\),即把\(j\)的第\(now\)位由\(1\)改为\(0\),证明如下:
关于\(j\oplus lowbit(j)\)为把\(j\)的第\(now\)位由\(1\)改为\(0\)的证明(不想看的可以跳过下面两个段落):
由于\(lowbit(j)\)表示的是第\(now\)位为\(1\),第\(0\sim now-1\)位为\(0\)的值,即\(j\)的二进制表达式中最低位的\(1\)所对应的值,例如:\(lowbit((1001100)_2)=(100)_2\),\(lowbit((11110)_2)=(10)_2\)。
那么由\(\oplus\)的运算法则(同0异1)可得\(k=j\oplus 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)\)为由询问状态\(i\oplus (1<<j)\)转移到询问状态\(i\)的概率。
那么就是\(1\)除以询问状态\(i\oplus (1<<j)\)中\(0\)的个数(即还没询问的个数),即
\(1/(len-sum[i\oplus (1<<j)])\),即\(1/(len-sum[i]+1)\)。
再让\(tmp=f[i\oplus (1<<j)]/(len-sum[i]+1)\),即由无询问转移到询问状态为\(i\)的概率(且上一个询问状态为\(i\oplus(1<<j)\))。
然后让\(ans+=sum[i]*tmp*(num[i\oplus (1<<j)]-num[i])\)。
\(sum[i]\)为\(i\)中有多少个\(1\),即询问次数。
\(tmp\)就是概率。
\(num[i\oplus (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\n",ans/n);//因为选择每个串都是等概论事件,所以要除以一个n
return 0;
}
原文地址:https://www.cnblogs.com/ez-lcw/p/11491335.html