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

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) O(n2l)问题在于我不会期望

预处理出每个状态不确定的字符串的个数,就可以暴力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
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
NOI(全国青少年信息学奥林匹克竞)模拟测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值