noip2019集训测试赛(十二)A.记忆(memory)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/tylon2006/article/details/100048454

Description

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


Input

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


Output

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


HINT

设串长为l
对于20的数据, n,l≤10
对于30的数据, n,l≤15
对于60的数据, n,l≤20
对于100的数据, n≤50,l≤20


Solution

数据一眼预处理+状压, O ( n 2 l ) O(n*2^l) 问题在于我不会期望

预处理出每个状态不确定的字符串的个数,就可以暴力DP了。


Code

#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);  //a[j][k]存储第j位是k的字符串有哪些(状压保存)
		}
	}
	//num[ss]代表状态为ss时有多少个串 当答案为该串时不确定(总)
	num[0]=n;  //ss为0时全满
	for(int i=0;i<n;i++){
		//b[i][j]代表选定的字符串是i,状态为j时有多少个不确定的串(分)
		b[i][0]=(1LL<<n)-1LL; //j为0时全满
		for(int j=1;j<M;j++){
			int k=j^(j&-j);
			int pos=__builtin_ctz(j); //求出lowbit在j的二进制表达中的位置
			b[i][j]=b[i][k]&a[pos][s[i][pos]]; //用还没有lowbit的状态来更新有lowbit状态
			//b[i][j]=b[i][j^lowbit(j)]&a[最低位][串i的最低位这个位置的字符]
			//意思就是b[i][k]已经存储了其他位的不确定串有哪些,但要剔除掉最低位这个位置的字符与串i不同的串
			if(b[i][j]!=(b[i][j]&-b[i][j])) num[j]++;
			//因为b[i][j]必将包含自身i,所以若b[i][j]不止一位,即该串为答案且状态为j时不确定
		} 
	}
	for(int i=1;i<M;i++)
	cnt[i]=cnt[i>>1]+(i&1); //求出1~1<<m-1每个数的二进制表示中一的个数,也是操作次数
	f[0]=1; //f[x]统计转移到x的概率
	for(int i=1;i<M;i++)
	for(int j=0;j<m;j++)
	if((i>>j)&1){ //即根据二进制位一位一位枚举 若该位为1才进行操作
		long double tmp=f[i^(1<<j)]/(m-cnt[i]+1); 
		//cnt[i]是1的个数,那么m-cnt[i]+1就是0的个数,多加1是因为当前的cnt[i]比cnt[i^(1<<j)]多1,而我们求的是原串0的个数
		//显然该位为1的状态要由该位为0的状态转移到,即该位变成1,概率为f[i^(1<<j)]*(1/(m-cnt[i]+1))
		ans+=cnt[i]*(tmp*(num[i^(1<<j)]-num[i]));
		//cnt[i]是操作次数,括号中的是操作cnt[i]次的概率,即期望次数=操作次数*概率
		//tmp应该好理解,即转移过来的概率;
		//后面计算从不确定变成确定的串的个数,那么我们选其中任意一个串作为答案都可以转移到j状态
		//易知num[i^(1<<j)]>=num[i]
		f[i]+=tmp; //统计总概率
	}
	printf("%.10Lf",ans/n); //因为是等概率选择答案串,即概率为1/n
}

猜你喜欢

转载自blog.csdn.net/tylon2006/article/details/100048454