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\):
? 將所有人的健康值作為一種狀態,因為\(h\)最大為\(6\),所以可以將所有人的健康值狀態通過\(7\)進制數\(hash\)成一個整數,便于保存以及狀態的轉移,假設目前的狀態\(hash\)值為\(state\),健康值大于\(0\)的人數有\(sz\)個,列舉所有可能選擇的人,對于其中一個人,使其健康值減\(1\)后的狀態為\(nextstate\),則有\(dp[nextstate]=dp[state]\times \frac{1}{sz}\),經過\(d\)次之后所有狀態中,將對方人數為\(0\)的概率加起來即可,
但是有幾個問題:
- 最多會有\(10\)個人,所以可能需要\(7^{10}=3\times 10^8\)來保存狀態,存不下,但是可以發現,存盤狀態的時候,與所有人的健康值的大小順序無關,所以可以強制保證他們有序,這樣只需要\(2\times 10^5\),
- 轉換為\(7\)進制且每個狀態都對其排序后,最終無法知道那些人是對方的人,可以在保存的時候,將對方的人的健康值取負,這樣排序后對方的人一定排在最前端,轉換進制也改變為\(13\)進制,且對于每個人的健康值增加\(6\)的偏移值,保證健康值都是正數,這樣最終只要判斷一下最前端的人的健康值是否大于\(0\)即可,
方法\(2\):
? 根據健康值分類,統計每種健康值的人數(己方與對方的人分開統計),然后將\(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}\),
【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;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/139974.html
標籤:其他
