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

题面

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

时间: 2024-08-28 15:47:06

【XSY2518】记忆(memory)(状压dp,概率与期望,概率dp)的相关文章

HDU 5025 (BFS+记忆化状压搜索)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5025 题目大意: 迷宫中孙悟空救唐僧,可以走回头路.必须收集完钥匙,且必须按顺序收集.迷宫中还有蛇,杀蛇多耗时1,蛇杀完就没了.问最少耗时. 解题思路: 2014广州网赛的水题之一.当时没刷过BFS状压,结果悲剧了. 首先这题可以压钥匙,也可以压蛇,不过压钥匙内存岌岌可危,于是就压蛇吧. 设f[x][y][key][snake]为在(x,y)点,已经取得的钥匙key,以及杀蛇snake的状态. 对

HDU 1429 (BFS+记忆化状压搜索)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1429 题目大意:最短时间内出迷宫,可以走回头路,迷宫内有不同的门,对应不同的钥匙. 解题思路: 要是没有门和钥匙,而且不能走回头路,就是个简单粗暴的BFS. 有了门之后,就要状态压缩+记忆化搜索.不然这个图会搜死你. 本题的状态压缩基于一个事实:尽管可以走回头路,但是回头是有理由的,你要么开了门,要么拿了钥匙,使状态发生改变. 否则等于多绕了一步,浪费时间,应该及时剪枝. f[x][y][key]

zoj3640:概率(期望)dp

题目大意:有一个吸血鬼,初始攻击力为f,每天随机走到n个洞里面,每个洞有一个c[i],如果他的攻击力f>c[i] 则可以花费t[i] 的时间逃走,否则则花费一天时间使自己的攻击力增加c[i],求逃走天数的期望 分析: 这道题求期望,,考虑采用概率dp求解 想到的最简单方法就是dp[i][j]表示 第i天,攻击力为j的概率,然后对每一个c进行转移,最后统计答案 但是发现i,j的范围都是10000,n是100 这么做显然是行不通的 于是又可耻的搜了一下题解,发现有一个博主写的期望dp这个概念很不错

[AC自动机+状压dp] hdu 4534 郑厂长系列故事——新闻净化

题意:中文的题目,意思就是说有很多串,每个串都有权值,权值为999的串必须出现,-999的串必须不出现.权值在-999~999之间. 然后必须出现的串不超过8个.然后给一个全为小写目标串,问最少需要删除多少个字母才能够保证必须出现的串都出现,次数一样保证权值最大.输出次数和权值. 然后根据样例,那些必须出现的串,其实权值是0. 思路: 很明显一开始建自动机构成trie图,但是需要注意的就是mark和sum的更新.个人是把所有中间的节点的sum全部赋值成了-inf. 接着只有8个必须出现的串,所以

ACwing91 最短Hamilton路径 状压dp

网址:https://www.acwing.com/problem/content/93/ 题解: 状压之后暴力枚举更新.$dp[i][j]$表示$i$的二进制数中1的位置就是会经过的点,$j$的位置是当前的点.则转移方程是$dp[i][j]=min(dp[i][j],dp[i\oplus (1<<j)][k]+dis[k][j])$,其中$i\oplus (1<<j)$是二进制状态$i$去掉第$j$条边,即从一个没有与$j$直接连边的状态从$k$到$j$进行松弛,最后判断一下有边

概率与期望,组合

1.一个图论套路,结合之前的小知识(枚举子集) 例:Online judge 1268,Online judge 1396 询问一个图连通的方案数时,可令Dp一维为图是否连通,另一维是图的二进制表示(完全图可简化为点的个数),从而$Dp[x][0]$可通过$Dp[y][1]$来转移$y\subset x$ 具体来说,可令$x$中任意一个点为定点,枚举其中包含的联通块,同时其他点与该联通块之间不会有线段连接,按照题意列出方程可做到不重不漏.之后通过全集求出$Dp[x][1]$ #include <

UVA - 10817 Headmaster&#39;s Headache (状压dp+记忆化搜索)

题意:有M个已聘教师,N个候选老师,S个科目,已知每个老师的雇佣费和可教科目,已聘老师必须雇佣,要求每个科目至少两个老师教的情况下,最少的雇佣费用. 分析: 1.为让雇佣费尽可能少,雇佣的老师应教他所能教的所有科目. 2.已聘老师必须选,候选老师可选可不选. 3.dfs(cur, subject1, subject2)---求出在当前已选cur个老师,有一个老师教的科目状态为 subject1,有两个及以上老师教的科目状态为 subject2的情况下,最少的雇佣费用. dp[cur][subje

UVa 10817 (状压DP + 记忆化搜索) Headmaster&#39;s Headache

题意: 一共有s(s ≤ 8)门课程,有m个在职教师,n个求职教师. 每个教师有各自的工资要求,还有他能教授的课程,可以是一门或者多门. 要求在职教师不能辞退,问如何录用应聘者,才能使得每门课只少有两个老师教而且使得总工资最少. 分析: 因为s很小,所以可以用状态压缩. dp(i, s1, s2)表示考虑了前i个人,有一个人教的课程的集合为s1,至少有两个人教的集合为s2. 在递归的过程中,还有个参数s0,表示还没有人教的科目的集合. 其中m0, m1, s0, s1, s2的计算用到位运算,还

HDU 4336-Card Collector(状压,概率dp)

题意: 有n种卡片,每包面里面,可能有一张卡片或没有,已知每种卡片在面里出现的概率,求获得n种卡片,需要吃面的包数的期望 分析: n很小,用状压,以前做状压时做过这道题,但概率怎么推的不清楚,现在看来就是基本的概率dp dp[s]表示获得卡片种数情况是s时期望包数,dp[(1<<n)-1]=0,dp[0]就是答案 dp[s]=sum(dp[s+(1<<j)]*p[j])+1+(1-tmp)*dp[s](tmp是未吃到的卡片的概率和) 移项化简即可 #include <map&