zoukankan      html  css  js  c++  java
  • 2018.8.1 状压 CF482C 题解

    noip2016考了一道状压dp,一道期望dp

    然而这题是状压期望dp...

    所以难度是什么,省选noi吗...

    怎么办...

    题目大意:

    给定n个字符串,甲从中任选出一个串(即选出每个串的概率相同为1/n),乙要通过询问甲选出的字符串pos位置上的字符是什么来确定这个串。然而由于有些字符串的一些位置上字符相同,所以可能不能通过一次询问达成目标。现在乙没有任何策略地进行随机询问,问乙能够确定答案的询问次数期望是多少?

    其中n<=50,每个字符串长度相等且长度<=20

    题解:

    首先状压是比较好想的,但是和期望结合起来了,我们怎么办呢?

    那么我们先尝试对询问进行状压,这也是最显然的一个思路:记1表示该位置已被询问过,0表示该位置未被询问过。

    那么我们考虑转移:设状态为dp[i],字符串长度为l,那么dp[i]+=dp[i|(1<<j)]*1/tot

    看懵了?

    首先我们要知道,这是个期望dp,状态表示的是已经问了i状态的问题,距离得到想要的字符串的期望距离是多少

    于是显然我们从后向前更新。

    接下来,我们设j是i状态中没问过的一个问题,那么dp[i]就可以由dp[i|(1<<j)]转移过来

    而设i状态中没问过的问题数为tot,那么问出问题j的概率就是1/tot

    但...你有没有觉得缺点什么?

    是啊,这样根本忽略掉了字符串,变成了无论你读入什么串结果都一样,因为转移中根本没体现出字符串的存在!!!

    所以这个转移显然是并不完全的。

    那么我们要考虑一下完善它

    我们维护另一个数组s,用s[i]表示问i状态的问题有多少字符串是彼此区分不开的

    然后在转移dp时,我们使用这个方程:dp[i]=(∑dp[i|(1<<j)]*1/tot*num[i|(1<<j)]/num[i])+1

    这样就比较完善了。

    至于证明:前半部分不说了,我们就说一下后面除法的部分:

    其实这个方程可玄学了,网上其他方程都和这个不一样,但这是最简洁的一个...

    稍微证明一下,就是说当状态为i时,待确定的串有num[i]个,而当状态为(1<<j)|i时,待确定的状态有num[(1<<j)|i]个

    这样一来,无法确定所求串的概率就是num[(1<<j)|i]/num[i],所以乘上这个概率就好了。

    至于num数组如何维护,首先我们记录某一状态i下有哪些串不能确定,那么如果询问为i的子集这些串显然也不能确定,那么我们再把所有的子集查出来维护一下就好了。

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #define ll long long
    using namespace std;
    int num[(1<<20)+5];
    double dp[(1<<20)+5];
    ll unusage[(1<<20)+5];
    int n,l;
    char ch[55][25];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=0;i<n;i++)
    	{
    		scanf("%s",ch[i]);
    	}
    	if(n==1)
    	{
    		printf("0.000000000
    ");
    		return 0;
    	}
    	l=strlen(ch[1]);
    	for(int i=0;i<n;i++)
    	{
    		for(int j=i+1;j<n;j++)
    		{
    			ll sit=0;
    			for(int k=0;k<l;k++)
    			{
    				if(ch[i][k]==ch[j][k])
    				{
    					sit|=(1ll<<k);
    				}
    			}
    			unusage[sit]|=(1ll<<i);
    			unusage[sit]|=(1ll<<j);
    		}
    	}
    	for(int i=(1<<l)-1;i>=1;i--)
    	{
    		for(int j=0;j<l;j++)
    		{
    			if(i&(1<<j))
    			{
    				unusage[i^(1<<j)]|=unusage[i];
    			}
    		}
    	}
    	for(int i=0;i<(1<<l);i++)
    	{
    		for(int j=0;j<n;j++)
    		{
    			if(unusage[i]&(1ll<<j))
    			{
    				num[i]++;
    			}
    		}
    	}
    	dp[(1<<l)-1]=0;
    	for(int i=(1<<l)-2;i>=0;i--)
    	{
    		if(!num[i])
    		{
    			dp[i]=0;
    			continue;
    		}else
    		{
    			int tot=l;
    			for(int j=0;j<l;j++)
    			{
    				if((1<<j)&i)
    				{
    					tot--;
    				}
    			}
    			for(int j=0;j<l;j++)
    			{
    				if((1<<j)&i)
    				{
    					continue;
    				}
    				dp[i]+=dp[i|(1<<j)]*1.0/(double)(tot)*(double)(num[i|(1<<j)])/(double)(num[i]);
    			}
    			dp[i]+=1.0;
    		}
    	}
    	printf("%.9lf
    ",dp[0]);
    	return 0;
    }
  • 相关阅读:
    拍皮球 (Java实现)
    余弦 (java实现)
    循环输出
    从1输出n位数字
    数值的整数次方
    旋转数组的最小数字
    简单使用栈实现队列
    重建二叉树
    链表逆序输出
    替代空格
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764233.html
Copyright © 2011-2022 走看看