[补档]noip2019集训测试赛(十二)

Problem A: 记忆(memory)

Time Limit: 1000 ms Memory Limit: 512 MB

Description

你在跟朋友玩一个记忆游戏。

朋友首先给你看了n个长度相同的串,然后从中等概率随机选择了一个串。

每一轮你可以询问一个位置上的正确字符,如果能够凭借已有的信息确定出朋友所选的串,那么游戏就结束了,你的成绩就是所用的轮数。

由于你实在太笨,不会任何策略,因此你采用一种方法,每次等概率随机询问一个未询问过的位置的字符。

现在你想知道,在这种情况下,你猜出结果所需的期望次数。

Input

第1行包含一个整数 n,表示串的个数。

第 2 ~ 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

Solution

期望DP

考虑状压,a[i][j]表示在第i位上为字符j的个数,这个个数用一个二进制状态表示,第k位是1即表示k串第i位是字符j。

设b[i][j]代表在询问状态为j且答案为i的情况下,还有b[i][j]个字符串满足要求,同样以二进制状态储存。

设num[i],表示在询问状态为i的情况下,有num[i]个满足条件的字符串。

设i代表答案为第i个字符串,j代表当前询问状态,不难得到转移方程:

\(设k=j \bigoplus lowbit(j) pos为lowbit的位置\)

$ b[i][j] = b[i][k] and a[pos][s[i][pos]] $

\(num[j]+=[b[i][j]!=lowbit(b[i][j])]\)

第二条含义:如果pos位置上等于s[i][pos]的字符串同时也满足询问k的条件,显然它在询问j状态下也是满足的

第三条含义:如果有不确定的情况,那么num[j]++

接下来我们用cnt[i]表示二进制下i的1的个数,f[i]表示转移到状态i的概率

\(tmp=f[i \bigoplus (1<<j)]/(m-cnt[i \bigoplus (1<<j)])\),这代表了从\(i \bigoplus (1<<j)\)转移到i的概率(即1/上一状态的0的个数)

所以有\(f[i]+=tmp\)

接下来,我们有\(ans+=cnt[i]*(tmp*(num[i \bigoplus (1<<j)]-num[i]))\),这是什么意思呢?

首先cnt[i]是转移到i状态的操作个数(就是1的个数)

后面的tmp乘上的那一坨玩意儿就意味着你可以从\(num[i \bigoplus (1<<j)]-num[i]\)这些从不确定转为确定状态的串中任意一个进行转移

于是最后统计出来的ans就是所有串被猜中的期望操作次数总和

除以n就是最后答案了

#include<bits/stdc++.h>
using namespace std;
int n,m,M;
const int N=1<<20;
int num[N],cnt[N],s[50][20];
long long a[50][52],b[50][N];
long double f[N],ans;
char str[20];
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%s",str);
        m=strlen(str),M=1<<m;
        for(int j=0;j<m;j++){
            s[i][j]=('a'<=str[j]&&str[j]<='z')?str[j]-'a':str[j]-'A'+26;
            a[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<M;j++){
            int k=j^(j&-j);
            int pos=__builtin_ctz(j);
            b[i][j]=b[i][k]&a[pos][s[i][pos]];
            if(b[i][j]!=(b[i][j]&-b[i][j])) num[j]++;
        } 
    }
    for(int i=1;i<M;i++)
    cnt[i]=cnt[i>>1]+(i&1);
    f[0]=1;
    for(int i=1;i<M;i++)
    for(int j=0;j<m;j++)
    if((i>>j)&1){
        long double tmp=f[i^(1<<j)]/(m-cnt[i^(1<<j)]); 
        ans+=cnt[i]*(tmp*(num[i^(1<<j)]-num[i]));
        f[i]+=tmp;
    }
    printf("%.10Lf",ans/n);
}

猜你喜欢

转载自www.cnblogs.com/youddjxd/p/11434297.html
今日推荐