HDU 3718 Similarity(二分图最优匹配)
http://acm.hdu.edu.cn/showproblem.php?pid=3718
题意:
有两串字符串,他们都是分类的结果.第一行是正确结果,第二行是需要你判断的.比如:
A A B A B B C C CC
S T R S T R S T RS
当S对应A,T对应B,R对应C时, 它们匹配的位置个数=4
当S对应C,T对应A,B对应R时, 它们匹配的位置个数=5
明显第二种匹配方式更好,字符串相似率达到了5/10=50%.
现在给你很多字符串,要你输出最大相似率.
分析:
由于正确答案一定有K个不同的字母,但是学生的字符串字母个数<=K,所以这是一个左右子集大小不等的最优匹配问题.
左边点集放学生字符串的不同字母,右边点集放正确答案的不同字母.那么从左i(假设表’S’字母)到右j(假设表’A’字母)的边权值是多少呢? 该权值是两个字符串对应位置正好一个S和一个A的这种位置的总数和.(想想是不是,也即如果S与A对应,最多能蒙对多少个位置)
最终我们求得的最优匹配权值/字符串长度 即相似率.
AC代码:
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
const int maxn = 26+5;
struct Max_Match
{
int n,m,W[maxn][maxn];
int Lx[maxn],Ly[maxn];
bool S[maxn],T[maxn];
int left[maxn];
bool match(int i)
{
S[i]=true;
for(int j=1;j<=m;j++)if(Lx[i]+Ly[j]==W[i][j] && !T[j])
{
T[j]=true;
if(left[j]==-1 || match(left[j]))
{
left[j]=i;
return true;
}
}
return false;
}
void update()
{
int a=1<<30;
for(int i=1;i<=n;i++)if(S[i])
for(int j=1;j<=m;j++)if(!T[j])
a=min(a, Lx[i]+Ly[j]-W[i][j]);
for(int i=1;i<=n;i++)if(S[i]) Lx[i]-=a;
for(int j=1;j<=m;j++)if(T[j]) Ly[j]+=a;
}
int solve(int n,int m)
{
this->n=n;
this->m=m;
memset(left,-1,sizeof(left));
memset(Lx,0,sizeof(Lx));
memset(Ly,0,sizeof(Ly));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Lx[i] = max(Lx[i], W[i][j]);
for(int i=1;i<=n;i++)
{
while(true)
{
memset(S,0,sizeof(S));
memset(T,0,sizeof(T));
if(match(i)) break;
else update();
}
}
int ans=0;
for(int i=1;i<=m;i++)if(left[i]!=-1) ans+= W[left[i]][i];
return ans;
}
}KM;
char str1[10000+10],str2[10000+10];
char c1[maxn],c2[maxn];//分别代表左右点集
int main()
{
int T; scanf("%d",&T);
while(T--)
{
int n,k,m;
scanf("%d%d%d",&n,&k,&m);
for(int i=0;i<n;i++) scanf(" %c",&str2[i]);
while(m--)
{
for(int j=0;j<n;j++) scanf(" %c",&str1[j]);
int num1=0,num2=0;
bool vis[maxn];
memset(vis,0,sizeof(vis));
for(int j=0;j<n;j++)if(!vis[str1[j]-'A'])
{
vis[str1[j]-'A']=true;
c1[++num1]= str1[j];
}
memset(vis,0,sizeof(vis));
for(int j=0;j<n;j++)if(!vis[str2[j]-'A'])
{
vis[str2[j]-'A']=true;
c2[++num2]= str2[j];
}
for(int j=1;j<=num1;j++)
for(int k=1;k<=num2;k++)
{
KM.W[j][k]=0;
for(int h=0;h<n;h++)
if(str1[h]==c1[j] && str2[h]==c2[k]) KM.W[j][k]++;
}
int ans = KM.solve(num1,num2);
printf("%.4lf\n",1.0*ans/n);
}
}
return 0;
}