题解:P8079 [WC2022] 猜词(民间数据)
信息论
每次猜词,必然是要选择能获得的“信息”最多的词。但我们如何衡量信息的多少呢?
举个例子。扔一个骰子,下面有三个随机事件:
- 1.点数大于 0
- 2.点数小于 3
- 3.点数为 6
这三个事件的信息量大小如何?相信凭直觉也知道,信息量依次增大。
来想想信息的作用是什么,信息的作用是消除不确定性。所以消除不确定性的多少,决定了一件事带来信息的多少,这就是一个随机事件的信息量。
事件一必然发生,所以它没带来任何信息。事件二有
信息量和概率的具体关系又是什么样的呢?假设现在有 0 或 1,那么你需要传递几次信号才能让我知道?显然,用
但是,我们猜一个单词,所获得的结果可能是不一样的,可能获取不同的信息量。此时就需要信息熵,简单地说它表示信息量的期望。根据期望的计算方法可以得到信息熵。
主要思路
于是我们获得了一个想法。对于每个
每次猜信息熵最大的词即可。
优化
第一次猜的单词需要打表,否则时间复杂度太大。
打完了表,交上去,发现这么做只能 97 分,还需要小小优化一下。观察得分情况发现,我们需要平均 3 次之内才出来才可以得到满分。而上述这种做法,很多情况下,2 次猜完,备选答案的个数还剩 2 到 5 个。一般需要 3 次才能确定答案,第 4 次输出。我们可以加一点小优化。
比如当前
在筛选最佳单词时,对于每个单词,它如果在
这样就可以通过本题了。
代码
// #include"word.h"
#include<bits/stdc++.h>
using namespace std;
const string first_guess[26]={"slier","lares","lares","tores","tarns","arles","lares","lares","snare","ousel","ranis","nares","tares","aides","tries","lares","raise","aides","plate","nares","snare","riles","nares","cones","kanes","aeons"};
const int N=8869;
int cnt,pattern[243];
vector<string> word,ans;
string query;
void init(int num_scramble,const char *scramble){
for(int i=0;i<N;i++){
string _s;
for(int j=0;j<5;j++)_s.push_back(scramble[i*5+j]);
word.push_back(_s);
}
}
int judge(string s,string t){
int res[5]={0,0,0,0,0};
for(int i=0;i<5;i++)if(s[i]==t[i])res[i]=2;
for(int i=0;i<5;i++){
if(res[i]==2)continue;
for(int j=0;j<5;j++){
if(res[j]!=2&&s[i]==t[j]){
res[i]=1;
break;
}
}
}
int _=0;
for(int i=0;i<5;i++)_=_*3+res[i];
return _;
}
bool match(string s,string t,bool *gold,bool *silver){
int cor=0;
for(int i=0;i<5;i++){
cor*=3;
if(gold[i])cor+=2;
if(silver[i])cor++;
}
return cor==judge(s,t);
}
double calc_entropy(string s,int remaining_guesses){
memset(pattern,0,sizeof(pattern));
for(string t:ans)pattern[judge(s,t)]++;
double E=0;
for(int i=0;i<243;i++)if(pattern[i]>0){
double P=1.0*pattern[i]/cnt;
E-=P*log2(P);
}
if(remaining_guesses==4)E+=0.1*pattern[242];
else if(remaining_guesses<=3)E+=1.0*pattern[242]/cnt;
return E;
}
const char *guess(int num_testcase,int remaining_guesses,char initial_letter,bool *gold,bool *silver){
if(remaining_guesses==5){
ans.clear();
for(string s:word)if(s[0]==initial_letter)ans.push_back(s);
cnt=ans.size();
query=first_guess[initial_letter-'a'];
return query.c_str();
}
vector<string> last;
last=ans;
ans.clear();
for(string t:last)if(match(query,t,gold,silver))ans.push_back(t);
cnt=ans.size();
if(cnt==1)return (query=ans[0]).c_str();
double mx=-114514.1919810;
for(int i=0;i<word.size();i++){
double entro=calc_entropy(word[i],remaining_guesses);
if(entro>mx)mx=entro,query=word[i];
}
return query.c_str();
}