在如今的信息社会中,时间-就是生命,对于记者们来说,如何以最快的速度传递消息就显得十分重要了,而为了尽快记录消息内容,速记也是必不可少的。速记就是用一些简单且特殊的符号表示一定的含义,具体如何对应依个人习惯而定,没有一种固定的表示方法。
Tom是一名报社的新闻记者,常常马不停蹄的跟着新闻跑,有时只能随手记下采访的内容,让人送回报社,而自己又奔赴下一个现场。不过Tom是一个糊涂的记者,有时忙中出错,把用自己的速记符号写的内容直接传回报社。因为一时联系不上Tom,但这条新闻又十分重要,要赶着在当天的报纸排版前整理出来,于是Tom的同事们只好来猜测Tom的速记符号的意思。幸运的是Tom的同事们与他共事的时间也不短了,对于Tom的一些用词情况有一定的了解,经过讨论,他们列出了一张可能性表来表示每一个速记符号可能与哪些单词相对应,并列出了对应的可能性有多大。
你的任务是:根据Tom的同事们提供的可能性表,找出一种可能性最大的速记符号与单词的对应方法(可能性应该相乘来计算)。
注意:每一个速记符号有且只有一个单词与其对应,每一个单词有不超过一个速记符号与其对应(可能没有速记符号与之对应)。
输入描述 Input Description
文件的第一行有两个整数,分别为速记符号的个数n(1<=n<=100)和单词总m (1<=m<=500)。
从第1行到第n+1行为每个速记符号可能对应的单词及其可能性。
第i+1(1<=i<=n)行的第一个数Ci表示第i个速记符号可能与Ci个单词相对应,后面有Ci个数对(Nik,Rik)(1<=k<=Ci),表示第i个速记符号与第Nik个单词相对应的可能性为Rik(Rik为大于0小于1的实数)。
输出描述 Output Description
输出文件仅包含一行,若有解则输出一个实数即最大的可能性,保留四位有效数字(四舍五入),若无解则输出"NO ANSWER"。
(当可能性大于1e-12时才被视为有解)
样例输入 Sample Input
3 3
2 1 0.4 3 0.2
1 3 0.8
3 1 0.1 2 0.9 3 0.2
样例输出 Sample Output
0.2880
j就是求个最大权匹配,但是是相乘最大,而不是一般的和最大.所以先转换成以10为底的对数,找出和最大的匹配.最后在把答案转换出来. 恶心的是要保留4位有效数字. 如果单纯用c++的保留4位小数,当数字太小时,是按指数形式输出,不符合题目. 所以要手动处理 (本人也不懂怎么实现的,参考大神的c++手动处理) ;
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <bitset>
#include <cmath>
#include <sstream>
using namespace std;
const char fi[] = "sign.in";
const char fo[] = "sign.out";
const int maxN = 510;
const double MAX = 1e198;
const double MIN = -MAX;
const double zero = 1e-12;
double w[maxN][maxN];
double lx[maxN], ly[maxN];
//std::bitset <maxN> mx, my;
bool my[maxN],mx[maxN];
int Link[maxN];
int n, m, t;
double ans, c;
void readdata()
{
scanf("%d%d", &n, &m);
for (int i = 1; i < n + 1; ++i)
lx[i] = MIN;
for (int j = 1; j < m + 1; ++j)
ly[j] = 0;
for (int i = 1; i < n + 1; ++i)
for (int j = 1; j < m + 1; ++j)
w[i][j] = MIN;
for (int i = 1; i < n + 1; ++i)
{
scanf("%d", &t);
for (; t; --t)
{
int j;
scanf("%d%lf", &j, &c);
c = log10(c); //以10為底取对数。
w[i][j] = c;
lx[i] = max(lx[i], w[i][j]);
}
}
}
bool Find(int u)
{
mx[u]=true;
for (int v = 1; v < m + 1; ++v)
{
if (fabs(lx[u] + ly[v] - w[u][v]) //这里顺便剪枝,不然会超时
< zero && !my[v])
{
my[v]=true;
if (!Link[v] || Find(Link[v]))
{
Link[v] = u;
return true;
}
}
}
return false;
}
inline void print(double d, int prec)
{
stringstream ss1, ss2;
string text = "";
ss1 << d;
ss1 >> text;
if (text.find('e') != string::npos)
//如果數字太小,則會使用指數形式表示出來,
//這樣就需要作一定處理。
{
string s1 = text.substr(0, text.find('e'));
string s2 = text.substr(text.find('e') + 1);
string s3 = "";
ss2 << s2;
int bit;
ss2 >> bit;
cout << "0.";
for (int i = 0; i < -bit - 2; ++i)
cout << '0';
if (s1.find('.') != string::npos)
{
s3 = s1.erase(s1.find('.'), s1.find('.'));
s3 = '0' + s3;
while (s3.size() <= prec) s3 += '0';
//處理末尾的0補足。
if (s3[prec + 1] >= '5')
{
++s3[prec];
for (int i = prec; i; --i)
if (s3[i] > '9')
{s3[i] = '0'; ++s3[i - 1]; }
}
//處理末尾的四捨五入。
if (s3[0] > '0')
s3.erase(s3.begin() + prec, s3.end());
else s3.erase(s3.begin() + prec + 1, s3.end());
//若最高一位有進位,則多去掉一個最低位。
cout << s3;
return;
}
cout << s1;
return;
}
cout << setiosflags(ios::showpoint)
<< setprecision(prec) << d;
//如果數字不太小,則不需要處理,
//直接按精度輸出,且強制顯示末尾的0。
}
void work()
{
for (int k = 1; k < n + 1; ++k)
while (1)
{
memset(mx,false,sizeof(mx));
memset(my,false,sizeof(my));
if (Find(k)) break;
double Min = MAX;
for (int i = 1; i < n + 1; ++i)
if (mx[i])
for (int j = 1; j < m + 1; ++j)
if (!my[j])
Min = min(Min, lx[i]
+ ly[j] - w[i][j]);
for (int i = 1; i < n + 1; ++i)
if (mx[i]) lx[i] -= Min;
for (int j = 1; j < m + 1; ++j)
if (my[j]) ly[j] += Min;
}
for (int j = 1; j < m + 1; ++j)
ans += w[Link[j]][j];
ans = pow(10, ans); //使用指數還原。
if (ans > zero) print(ans, 4);
//輸出ans並保留4位有效數字。
else cout << "NO ANSWER";
}
int main()
{
readdata();
work();
exit(0);
}