codeforces 482c 状压+概率DP

题意:给出N个不同的串,长度一样,别人随机选一个串,你要询问他那个串某一个位置是什么字符直到能确定那个串才能停止,问询问次数的期望。

题解:50个串20个位置容易想到状压,把字符串长度状压先考虑能否在某一个状态确定哪些字符串能确定哪些不能确定,需要2^m*m次,然后时间上不能再乘以n不然会爆,想想只要我知道到达某一个猜位置状态的概率dp[i],再知道相对应有哪些字符串可以确定和不可以确定,用f[i]来表示,那么对于不能确定的字符串相当于就要再猜一步,那么加上这个状态的概率就行了,不会再需要乘以n。

求每个状态对应有哪些可以确定的方法可以把两个字符串两两对比起来,对于所有相等位置求出这些位置也不能确定则标记为1,否则标记为0,这样一圈标记下来之后对于f[i]数组如果在i状态下两个字符串不能互相识别,那么必定被标记为1,当然如果对于f[i]如果某个状态的是包含于另一个状态的则要进行或运算,说着有点麻烦,不过看一下代码就清楚了

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define EPS 1e-10
typedef long long ll;
const int maxn = (1<<20) + 10;
const int INF = 1e9 ;
const double eps = 1e-8;
const int mod = 2520;
ll f[maxn],bin[60];
double dp[maxn];
int n;
char ss[55][25];
int cal(ll u){
    int cnt = 0;
    while(u > 0){
        if(u % 2 == 1) cnt++;
        u /= 2;
    }
    return cnt;
}
int main(){
  //  freopen("in.txt","r",stdin);
    cin>>n;
   // for(int i = 1;i <= 20;i++)
    if(n == 1){
        cout<<0.000000000<<endl;
        return 0;
    }
    for(int i = 0;i < n;i++)
        scanf("%s",ss[i]);
    bin[0] = 1;
    for(int i = 1;i <= 50;i++) bin[i] = bin[i-1]*2;
    int m = strlen(ss[0]);
    for(int i = 0;i < n;i++)
    for(int j = 0;j < n;j++){
        if(i != j){
            int same = 0;
            for(int k = 0;k < m;k++)
                if(ss[i][k] == ss[j][k])
                    same |= (bin[k]);
            f[same] |= (bin[j]);
        }
    }
    //cout<<(1<<40)<<endl;
    int se =(1<<m) - 1;
    for(int i = se;i >= 0;i--){
        for(int j = 0;j < m;j++)
            if(i & bin[j])
            f[i^bin[j]] |= f[i];
    }
    f[0] = bin[n] - 1;
   // for(int i = 0;i <= se;i++)
      //  cout<<i<<" "<<f[i]<<endl;
    double res = 0.0;
    dp[0] = 1.0;
    for(int i = 0;i <= se;i++){
        double cnt = m - cal((ll)i) + 1;
        for(int j = 0;j < m;j++){
            if((i&bin[j])&&f[i^bin[j]] > 0){
               // double cnt1 = cal(f[i^bin[j]]);
                dp[i] += dp[i^bin[j]]*(1.0/cnt);
            }
        }
        //cout<<i<<" "<<dp[i]<<endl;
    }
    for(int i = 0;i <= se;i++){
        for(int j = 0;j < n;j++)
            if(f[i]&bin[j])
                res += dp[i];
    }
    printf("%.10f\n",res/n);
    return 0;
}

还有我利用公式求出dp[i],i相对应猜了x次,猜中了的概率为y这三者相乘不行,不知道为什么,先贴一下代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define EPS 1e-10
typedef long long ll;
const int maxn = (1<<20) + 10;
const int INF = 1e9 ;
const double eps = 1e-8;
const int mod = 2520;
ll f[maxn];
double dp[maxn];
int n;
char ss[55][25];
int cal(ll u){
    int cnt = 0;
    while(u > 0){
        if(u % 2 == 1) cnt++;
        u /= 2;
    }
    return cnt;
}
int main(){
  //  freopen("in.txt","r",stdin);
    cin>>n;
    for(int i = 0;i < n;i++)
        scanf("%s",ss[i]);
    int m = strlen(ss[0]);
    for(int i = 0;i < n;i++)
    for(int j = 0;j < n;j++){
        if(i != j){
            int same = 0;
            for(int k = 0;k < m;k++)
                if(ss[i][k] == ss[j][k])
                    same |= (1<<k);
            f[same] |= (1<<j);
        }
    }
    int se =(1<<m) - 1;
    for(int i = se;i >= 0;i--){
        for(int j = 0;j < m;j++)
            if(i & (1<<j))
            f[i^(1<<j)] |= f[i];
    }
    f[0] = (1<<n) - 1;
   // for(int i = 0;i <= se;i++)
      //  cout<<i<<" "<<f[i]<<endl;
    double res = 0.0;
    dp[0] = 1.0;
    for(int i = 0;i <= se;i++){
        double cnt = m - cal((ll)i) + 1;
        for(int j = 0;j < m;j++){
            if(i & (1<<j)&&f[i^(1<<j)] > 0){
                double cnt1 = cal(f[i^(1<<j)]);
                dp[i] += dp[i^(1<<j)]*(1.0/cnt)*cnt1/n;
            }
        }
        //cout<<i<<" "<<dp[i]<<endl;
    }
    for(int i = 0;i <= se;i++){
        res += dp[i]*(cal((ll)i))*(n - cal(f[i]))/n;
        //cout<<i<<" "<<dp[i]*(cal((ll)i))*(n - cal(f[i]))/n<<endl;
    }
    printf("%.10f\n",res);
    return 0;
}

 

转载于:https://www.cnblogs.com/shimu/p/5923275.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值