Codeforces 482C Game with strings

  • 题意:给你n个长度为l的字符串, 1<=n<=50 , 1<=l<=20 ,选出一个字符串,每一次操作你可以知道这个字符串某个位置的字符,问多少次可以操作可以唯一确定这个字符串是哪个。求出对于所有字符串,可以唯一确定一个字符串的操作数的期望。
  • 思路:这是一个概率题,概率题最重要的就是不重复不遗漏。因为这里l的长度只有20,所以可以考虑枚举每一次猜的哪个位置。比如对于abc,abd这两个字符串,知道1,2两个位置都是不能知道具体是哪一个的,可以看出在一个字符串不能被确定的时候,前面的操作的顺序是无关的,因此枚举所有的操作的复杂度为 l2l 。确定一个特定的操作能确定哪些字符串可以使用状态压缩的方法来解决。具体实现见代码以及注释。
  • 代码:
//注释写的很长,请复制到编辑器里面全屏查看。
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int MAXN = 50 + 5;
const int MAXM = 20;
LL identity[1<<MAXM];

int n;
char s[MAXN][MAXM + 10];

int main()
{
    scanf("%d", &n);
    getchar();
    //input
    for(int i = 0; i < n; i++) {
        scanf("%s", s[i]);
    }
    int len = strlen(s[0]);

    for(int i = 0; i < n; i++) {
        for(int j = i + 1; j < n; j++) { //枚举所有字符串的两两组合
            int mask = 0;           //mask表示一个确定的操作,比如 00101表示知道了第三位和第一位的字符
            for(int k = 0; k < len; k++) {
                if(s[i][k] == s[j][k]) {
                    mask |= (1<<k); //如果当前两个字符串该位相同,这将mask对应的位置1,最后mask表示通过当前操作无法区分这两个字符串。
                }
            }
            identity[mask] |= (1LL<<i)|(1LL<<j); //把i,j加进通过mask操作无法确定的字符串集合里,这里同样是状态压缩
        }
    }


    for(int mask = (1<<len) - 1; mask >= 0; mask--) {
        for(int i = 0; i < len; i++) {
            if((mask>>i)&1) { //这里是一个dp,通过这一步,
                              //dentity[mask]表示的就是所有通过mask操作均不能确定的字符串集合。(上面一步只是确定了刚好通过mask操作不能确定的集合
                identity[mask^(1<<i)] |= identity[mask]; //如果00110操作不能确定的字符串,很明显00100,00010操作也不能确定
            }
        }
    }

    //计算每一步操作能够确定的字符串的数量,进而计算出期望
    long double ans = 0;
    for(int mask = 0; mask < (1<<len); mask++) {
        int moves = __builtin_popcount(mask) + 1; //这里为什么+1下面解释
        for(int k = 0; k < len; k++) {
            if(!((mask>>k)&1)) { //如果mask的第k位为0
                LL dif = identity[mask]^identity[mask^(1<<k)]; //这里列子,如果00110不能确定a,b,c三个字符串,而00111不能确定a,b两个字符串,
                                                                //很明显可以得出c可以通过00111这个操作确定。这里通过异或就可以得出通过mask|k的操作可以确定的字符串
                                                                //所以上面的操作次数是 mask中的1的数量+1,因为还需要另外一步k
                if(dif == 0) continue;      //mask|k一个字符串都不能确定。
                int cnt = __builtin_popcountll(dif); //计算mask|k能确定的字符串数量
                long double curexp = cnt * moves; //因为现在算的是把所有字符串全部确定的期望,所以每一个字符串都需要moves次的操作
                for(int i = 0; i < moves - 1; i++) {
                    curexp *= (long double)(moves - i - 1) / (long double)(len - i); //操作数乘上当前操作发生的概率,P = {1/C_n^{moves-1}}*{1/n - (moves - 1)}
                }
                curexp /= (long double)(len - moves + 1); //依然是计算概率见上面的公式
                ans += curexp; //根据加法原理,当前操作的期望加到总的期望里面去
            }
        }
    }
    ans /= (long double)n; //通过上面操作ans计算出的是所有字符串需要的操作的期望之和,所以除以n才是一个字符串需要的操作数的期望。
    cout << fixed << setprecision(15) << ans << endl; //输出答案
}

你可能感兴趣的:(经典,codeforces,482c)