最近想提高一下编程能力,参加了腾讯编程马拉松,结果五道题却制作出来了一道,有点受打击,于是就在HDOJ上找题练一下,随便就找到了HDOJ2901,于是开始了此文。(一开始就做这个似乎有点难的题,我开始怀疑自己是否有点好高骛远了)
题目就不多说了,见http://acm.hdu.edu.cn/showproblem.php?pid=2901
大意是要从接收到的字符串中找出最可能的发送字符串,已知的是发送a/b接受a/b的概率矩阵和发送a/b再次发送a/b的概率
题目的举例就是以此列举所有的可能性,然后比较每一种可能性的概率,取最大者。
所以一开始,我的思路很单纯:收发有a种字符,接受字符串长度w最大为300,所以最终的可能性有aw种可能性(其实对于题目的极限来说30300一定是难以承受的,也不知道最终会怎么去判断是否AC),而我就开始很单纯地取用pow(a,w)函数来作为循环结束的条件,结果总是超时。
我最开始的代码如下:
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <map>
//#include <algorithm>
//#define DEBUG
using namespace std;
int main(int argc, int * argv[])
{
//fstream cin("aaa.txt");
int n,a,i,j,num_case;
char c;
float d;
string s;
map<char,int> map_ch;
cin>>n;
while(n--)
{
cin >> a; //读入字符种类数
char ch[30];//vector<char> ch(a);
for(i=0;i<a;i++)
{
cin>>c;
ch[i] = c;
map_ch[c] = i;
//cout<<ch[i]<<endl;
}
//读入接受错误概率和字符继承概率矩阵
//注意下面这一行:vector<int后两个">"之间要有空格!否则会被认为是重载">>"。
float REP[30][30];//vector<vector<float> > REP(a, vector<float>(a)); //Receiving Error Probabilities
for(i=0;i<a;i++)
for(j=0;j<a;j++)
{
cin>>d;
REP[i][j] = d;
//cout<<REP[i][j]<<" ";
}
float CSP[30][30];//vector<vector<float> > CSP(a, vector<float>(a)); //Character Succession Probabilities
for(i=0;i<a;i++)
for(j=0;j<a;j++)
{
cin>>d;
CSP[i][j] = d;
//cout<<CSP[i][j]<<" ";
}
//读入测试案例
cin>>num_case;
for(j=0;j<num_case;j++)
{
cin>>s;
int s_length = s.length();
// 把输入的数据转换成0~a-1 比如ab转换成01 bb转换成11
vector<int> s_a(s_length);
for(i=0;i<s_length;i++)
for(int k=0;k<a;k++)
if(ch[k] == s[i]) s_a[i] =k;
//s_a[i] = map_ch[s[i]];
#ifdef DEBUG
cout<<"s_a : ";
for(i=0;i<s_length;i++)
cout<<s_a[i];
cout<<endl;
#endif
float max_p = 0.0; //最大概率
vector<int>MaxLikely(s_length); //最终结果
int max_maybes = pow(a,s_length);
for(i=0;i<max_maybes;i++) //一共有pow(a,s_length)种可能
{
vector<int>s_maybe(s_length); //可能的字符串
int n_maybes = i;
for(intk=0;k<s_length;k++) //将i转化成a进制数存在s_maybe中
{
s_maybe[k] = n_maybes % a;
n_maybes /= a;
}
//reverse(s_maybe.begin(),s_maybe.end());
#ifdef DEBUG
cout<<"s_maybe : ";
for(k=0;k<s_length;k++)
cout<<s_maybe[k];
cout<<endl;
#endif
// 计算概率
float liklyhood = 1;
for(k=0;k<s_length;k++)
{
liklyhood *=REP[s_maybe[k]][s_a[k]];
#ifdef DEBUG
cout<<"REP:"<<s_maybe[k]<<s_a[k]<<""<<endl;
#endif
}
for(k=0;k<s_length-1;k++)
{
liklyhood *=CSP[s_maybe[k]][s_maybe[k+1]];
#ifdef DEBUG
cout<<"CSP:"<<s_maybe[k]<<s_a[k]<<""<<endl;
#endif
}
#ifdef DEBUG
cout<<"likelyhood ="<<liklyhood<<endl;
#endif
if(liklyhood > max_p)
{
max_p = liklyhood;
for(k=0;k<s_length;k++)
MaxLikely[k] = s_maybe[k];
//MaxLikely = s_maybe;
}
}
//reverse(MaxLikely.begin(),MaxLikely.end());
for(i=s_length-1;i>=0;i--)
cout<<ch[MaxLikely[i]];
cout<<endl;
}
}
return 0;
}
代码的质量不敢说,只能说思路还是比较清晰的,而且看了曾宗根的书也用了一些数据结构,算是练个手吧,当然结果还是比较悲剧的。
后来在一位大神(OJ名”给数据跪了”,在此对您表示由衷的感谢)的帮助下,看懂了他的代码的思路,并开始用他的思路去思考问题。
他的思路是可以说是分级讨论问题,因为从后往前来看,需要找出的字符串的最后一个(第w个)字符是什么只取决于它前面一个(第w-1个)字符是什么,而第w-1个字符是什么取决于第w-2个字符……如此就形成了一个级联的问题。
首先已知REP[a][b] (Receiving Error Probabilities)即接收错误概率矩阵——发送a接收b的概率
和CSP[a][b] (CharacterSuccession Probabilities)即字符后继概率矩阵——发送a在发送b的概率
程序大致思路如下,假设只接收到一个字符s[0],很容易根据REP[a][s[0]]、REP[b][s[0]]来判断发送的是a还是b,如果此时再接收到一个字符s[1],就要进行讨论了
假设接收到的s[1]对应于发送a,对于前一个字符对应于a还是b,进行如下讨论:
如果是a,其概率为REP[a][s[0]]×CSP[a][a]:发送a接收s[0]的概率乘以发送a再发送a的概率
如果是b,其概率为REP[b][s[0]]×CSP[b][a]:发送b接收s[0]的概率乘以发送b再发送a的概率
这时就可以根据以上乘积的大小来判断出当s[1]对应于发送a时s[0]对应于发送什么了,然后可以得到s[1]对应于发送a的概率为
max{REP[a][s[0]]×CSP[a][a],REP[a][s[0]]×CSP[a][a]}×REP[a/b][s[1]]
(其中a/b对于与乘积的较大者)
同理,可得到s[1]对应于发送b的概率为
max{REP[a][s[0]]×CSP[a][b],REP[a][s[0]]×CSP[a][b]}×REP[a/b][s[1]]
(其中a/b对于与乘积的较大者)
并且当s[1]对应于发送a时s[0]对应于发送以上max函数中的较大者
如果继续接收到第三个字符,同样可以进行以上假设:
假设接收到的s[2]对应于发送a,对于前一个字符对应于a还是b,进行如下讨论:
如果是a,其概率为P(第二个字符是a的概率)×CSP[a][a]:第二个字符是a的概率乘以发送a再发送a的概率
如果是b,其概率为P(第二个字符是b的概率)×CSP[b][a]:第二个字符是b的概率乘以发送b再发送a的概率
而其中的P(第二个字符是a的概率)已经在前文中求的,所以可以用循环实现。
本算法的复杂度为a×a×w,远比aw小很多。(其中a是字符种类,w是字符串的长度)
我最终的代码如下:
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
fstreamcin("aaa.txt");
intn,a,i,j,num_case;
map<char,int>MAP_CH;
charc;
doubled;
chars[300];//string s;
cin>>n;
while(n--)
{
cin>> a; //读入字符种类数
charch[30];//vector<char> ch(a);
for(i=0;i<a;i++)
{
cin>>c;
ch[i]= c;
MAP_CH[c]= i;
//cout<<ch[i]<<endl;
}
//读入接受错误概率和字符继承概率矩阵
//注意下面这一行:vector<int后两个">"之间要有空格!否则会被认为是重载">>"。
doubleREP[30][30];//vector<vector<double> > REP(a, vector<double>(a)); //Receiving Error Probabilities
for(i=0;i<a;i++)
for(j=0;j<a;j++)
{
cin>>d;
REP[i][j]= d;
//cout<<REP[i][j]<<"";
}
doubleCSP[30][30];//vector<vector<double> > CSP(a, vector<double>(a)); //Character Succession Probabilities
for(i=0;i<a;i++)
for(j=0;j<a;j++)
{
cin>>d;
CSP[i][j]= d*10.0;
//cout<<CSP[i][j]<<"";
}
//读入测试案例
cin>>num_case;
while(num_case--)
{
cin>>s;
ints_length = strlen(s);//s.length();
vector<vector<double> > gl(s_length, vector<double>(a));
vector<vector<char> > answer(s_length, vector<char>(a));
for(i=0;i<a;i++)
gl[0][i] =REP[MAP_CH[ch[i]]][MAP_CH[s[0]]];
//cout<<gl[0][0]<<gl[0][1]<<endl;
for(int k=1; k<s_length;k++)
for(i=0;i<a;i++)
{
char c_last = ch[i]; //假设第k个字符是ch【i】
// 找出假设 c_last后的前一个字符(只可能有a种,对应概率为gl【k-1】【0~a-1】)的最大可能概率
double temp = 0;
for(j=0;j<a;j++)
{
if(temp< gl[k-1][MAP_CH[ch[j]]] * CSP[MAP_CH[ch[j]]][MAP_CH[c_last]])
{
temp = gl[k-1][MAP_CH[ch[j]]] * CSP[MAP_CH[ch[j]]][MAP_CH[c_last]];
answer[k][MAP_CH[c_last]] = ch[j]; //保存第k个字符是ch【i】时对应的第k-1个字符最大的可能的值
//cout<<answer[k][MAP_CH[c_last]]<<endl;
}
}
gl[k][MAP_CH[c_last]] =temp * REP[MAP_CH[c_last]][MAP_CH[s[k]]];
}
double temp = 0;
char ans_ch;
for(i=0;i<a;i++)
{
if(temp < gl[s_length-1][MAP_CH[ch[i]]])
{
temp =gl[s_length-1][MAP_CH[ch[i]]];
ans_ch =ch[i];//最后的一个最大概率字符
}
}
vector<char>s_answer(s_length);
s_answer[s_length-1]=ans_ch;
char ch_out = ans_ch;
for(i=s_length-2;i>=0;i--)
{ch_out = answer[i+1][MAP_CH[ch_out]];
s_answer[i]=ch_out; }
for(i=0;i<s_length;i++)
cout<<s_answer[i];
cout<<endl;
}
}
return0;
}
思路看懂后,很快就可以写程序了,但是有个比较纠结的问题,我发现读入CSP[][]矩阵(也就是发送字符后继概率矩阵)的时候,如果不保存为它的十倍(即*=10.0),提交的结果就是WrongAnswer,否则可以AC了。
这个问题困扰了我很久,自己的程序都快被改的乱七八糟了,百思不得其解,有点怀疑网站测试时的问题了,希望好心人给点提示。
再次感谢”给数据跪了”对我的启发。