首页 > 其他 > 详细

【未知来源】记忆

时间:2019-08-23 17:47:06      阅读:75      评论:0      收藏:0      [点我收藏+]

题意

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

题解

  起手写了个算错复杂度的 \(O(n!\times n^3)\) 暴力

  暴力 \(\text{dp}\) 很显然:因为除了最后一个询问点以外,之前的询问点无论按什么顺序访问,询问次数都是一样的,所以设 \(f(i,j)\) 表示答案串是第 \(i\) 个时,已经询问的点集为二进制数 \(j\),期望还需要询问多少次才能猜出答案。倒推,从每个非终止状态向前驱暴力转移(终止状态就是询问点集 \(j\) 后能确定答案是第 \(i\) 个串),最终答案是 \(\frac{\sum\limits_{i=1}^n f(i,0)}{n}\)。复杂度 \(O(2^l n^2)\),能(卡)过 \(60\) 分(为什么大家都觉得 \(1s\) 随便跑 \(4e8\) 鸭)

  正解思路比较巧妙(然而 zjr 说是个套路)。
  我们有一个 \(O(n^2 l)\) 的方法预处理 \(g(i)\),表示询问点集 \(i\) 后不能确定的字符串集合(用一个 \(n\) 位二进制数表示)。
  然后设 \(f(i)\) 表示询问点集 \(i\) 后,对于 \(n\) 种答案串的情况 期望还需要询问的次数之和。
  类似于暴力倒推,把状态看成一个 \(\text{DAG}\),一个状态(点集)有很多个后继状态,状态 \(i\)\(f(i)\) 即为所有后继状态 \(j\)\(f(j)\) 的平均数 加上询问状态(点集)\(i\) 不能确定的字符串集合(感性理解就是,对于每个不确定的字符串为答案的情况,都需要询问一次才能到达后继状态,有 \(k\) 个不确定的字符串询问次数就加 \(k\))。
  最终答案是 \(\frac{f(0)}{n}\)。复杂度 \(O(2^l n)\)
  (zjr 是神仙,不接受反驳)

#include<bits/stdc++.h>
#define ll long long
#define N 51
#define L 21
const int lim = (1<<20) - 1;
using namespace std;
inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    if(f) return x; return 0-x;
}
int n,len,bcnt[lim+5];
double f[1<<L]; ll g[1<<L];
char s[N][L];
inline int calc(ll x){
    int res=bcnt[x&lim];
    res+=bcnt[(x>>=20)&lim];
    res+=bcnt[(x>>=20)&lim];
    return res;
}
int main(){
    n=read();
    for(int i=1; i<=lim; ++i) bcnt[i]=bcnt[i>>1]+(i&1);
    for(int i=0; i<n; ++i) scanf("%s",s[i]);
    len=strlen(s[1]);
    for(int i=0; i<n; ++i)
        for(int j=0; j<n; ++j) if(i!=j){
            int tmp=0; 
            for(int k=0; k<len; ++k)
                if(s[i][k]==s[j][k]) tmp|=(1<<k);
            g[tmp]|=(1ll<<i), g[tmp]|=(1ll<<j);
        }
    for(int i=(1<<len)-1; i>=0; --i){
        int cnt=0;
        for(int j=0; j<len; ++j) if(!(i&(1<<j)))
            f[i]+=f[i|(1<<j)], g[i]|=g[i|(1<<j)], ++cnt;
        if(cnt) f[i]/=cnt;
        f[i]+=calc(g[i]);
    }
    printf("%.10lf\n",f[0]/n);
    return 0;
}

【未知来源】记忆

原文:https://www.cnblogs.com/scx2015noip-as-php/p/11401247.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!