2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)-E. Explosion Exploit-几率+状压dp

2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)-E. Explosion Exploit-几率+状压dp


【Problem Description】

我方有\(n\)我的,对方有\(m\)我的,每一个人都有一个健康值\(h_i\),有\(d\)次攻击,每次随机从全部人中选\(1\)我的,减小其\(1\)健康值。问将对方全部人都消灭的几率是多少?前端

【Solution】

方法\(1\)ios

​ 将全部人的健康值做为一种状态,由于\(h\)最大为\(6\),因此能够将全部人的健康值状态经过\(7\)进制数\(hash\)成一个整数,便于保存以及状态的转移。假设目前的状态\(hash\)值为\(state\),健康值大于\(0\)的人数有\(sz\)个,枚举全部可能选择的人,对于其中一我的,使其健康值减\(1\)后的状态为\(nextstate\),则有\(dp[nextstate]=dp[state]\times \frac{1}{sz}\)。通过\(d\)次以后全部状态中,将对方人数为\(0\)的几率加起来便可。spa

可是有几个问题:code

  1. 最多会有\(10\)我的,因此可能须要\(7^{10}=3\times 10^8\)来保存状态,存不下,可是能够发现,存储状态的时候,与全部人的健康值的大小顺序无关,因此能够强制保证他们有序。这样只须要\(2\times 10^5\)
  2. 转换为\(7\)进制且每一个状态都对其排序后,最终没法知道那些人是对方的人。能够在保存的时候,将对方的人的健康值取负,这样排序后对方的人必定排在最前端。转换进制也改变为\(13\)进制,且对于每一个人的健康值增长\(6\)的偏移值,保证健康值都是正数。这样最终只要判断一下最前端的人的健康值是否大于\(0\)便可。

方法\(2\)blog

​ 根据健康值分类,统计每种健康值的人数(己方与对方的人分开统计),而后将\(6\)种健康值对应的人数也经过\(7\)进制数\(hash\)到一个整数上,加上对方的人总共最多须要\(12\)位,且高位存储对方每种健康值的人数,这样能够经过判断\(state\)是否小于等于\(all=(666666)_7\),来肯定对方的人是否全被消灭。排序

而后就是转移,枚举健康值\(i\),每次从健康值为\(i\)的人里选\(1\)我的,将其健康值减\(1\),选择健康值为\(i\)的人的几率为\(\frac{h[i]}{sum}\),其中\(h[i]\)为,健康值为\(i\)的人数,\(sum\)为总人数。因此有\(dp[state]=dp[nextstate]*\frac{h[i]}{sum}\)ip


【Code】

//方法1
#include<iostream>
#include<iomanip>
#include<map>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define int long long
map<int,double>dp; //dp[state]表示从初始状态转换到状态state的几率为dp[state]
vector<int>v;
int encode(vector<int>v){ //hash为13进制
    int ans=0,tmp=1;
    for(auto vv:v){
        ans+=(vv+6)*tmp; //增长6的偏移值,保证都为正数
        tmp*=13;
    }
    return ans;
}
vector<int> decode(int state){ //求每一个人的健康值
    v.clear();
    while(state){
        v.push_back(state%13-6);
        state/=13;
    }
    sort(v.begin(),v.end()); //保证有序
    return v;
}
map<int,double> solve(){
    map<int,double>mp;
    for(auto v:dp){
        vector<int>state=decode(v.first);
        for(int i=0;i<state.size();i++){ //枚举每种选择,转移状态
            vector<int>copy(state);
            if(copy[i]>0) copy[i]--;
            else if(copy[i]<0) copy[i]++;
            if(copy[i]==0){ //将健康值为0的人去除
                copy.erase(copy.begin()+i); 
            }else{
                sort(copy.begin(),copy.end()); //保证健康值有序
            }
            mp[encode(copy)]+=v.second*1.0/state.size();
        }
    }
    return mp;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m,d,sum1=0;cin>>n>>m>>d;
    for(int i=1;i<=n;i++){
        int x;cin>>x;sum1+=x;
        v.push_back(x);
    }
    int sum2=0;
    for(int i=1;i<=m;i++){
        int x;cin>>x;sum2+=x;
        v.push_back(-x); //将对方的人健康值取负,以在最终的时候区别我方/对方
    }
    if(sum2>d){ //没法将对方全部人都消灭
        cout<<"0"<<endl;
        return 0;
    }
    if(sum1+sum2<=d){ //可将全部人都消灭
        cout<<"1"<<endl;
        return 0;
    }
    sort(v.begin(),v.end()); //保证有序
    dp[encode(v)]=1;
    for(int i=0;i<d;i++) dp=solve(); //转移d次
    double ans=0;
    for(auto v:dp){
        vector<int>t=decode(v.first);
        if(t.size()&&t[0]>0) ans+=v.second; //若是最小的人的健康值都大于0,则对方的人必定都被消灭
    }
    cout<<setiosflags(ios::fixed)<<setprecision(9);
    cout<<ans<<endl;
    return 0;
}
//方法2
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
#define maxn 10
#define INF 0x3f3f3f3f
#define int long long
int h1[maxn]/*己方健康值为i的人数*/,h2[maxn]/*对方健康值为i的人数*/,all=0/*临界值*/;
int encode(){ 
    int ans=0;
    for(int i=1;i<=6;i++) ans=ans*7+h2[i]; //对方放在高6位
    for(int i=1;i<=6;i++) ans=ans*7+h1[i]; //己方放在低6位
    return ans;
}
map<int,double>dp;
double dfs(int pos,int state){
    if(dp.count(state)) return dp[state];
    double &ans=dp[state],sum=0;
    if(state<=all) return ans=1; //若是高6位全为0,则对方全被消灭
    if(pos==0) return ans=0;
    for(int i=1;i<=6;i++){
        if(!h1[i]) continue;
        sum+=h1[i]; //总人数
        h1[i]--;h1[i-1]++; //当前血量的人减小一个,前一个血量的人增长一个,使得总血量只减小了1
        ans+=(h1[i]+1)*dfs(pos-1,encode());
        h1[i]++;h1[i-1]--; //注意恢复
    }
    for(int i=1;i<=6;i++){
        if(!h2[i]) continue;
        sum+=h2[i];
        h2[i]--;h2[i-1]++;
        ans+=(h2[i]+1)*dfs(pos-1,encode());
        h2[i]++;h2[i-1]--;
    }
    ans/=sum; //最后除以总人数
    return ans;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m,d;cin>>n>>m>>d;
    for(int i=1;i<=n;i++){
        int x;cin>>x;h1[x]++;
    }
    for(int i=1;i<=m;i++){
        int x;cin>>x;h2[x]++;
    }
    for(int i=1;i<=6;i++) all=all*7+6; //低6位全满时的最大值
    cout<<setiosflags(ios::fixed)<<setprecision(9);
    cout<<dfs(d,encode())<<endl;
    return 0;
}
相关文章
相关标签/搜索