题意:给出n个字符串,其中任意两个字符串(包括同一字符串)可以进行互相拼接起来,例如s1="abcd"……s2="dcab",表示将s1拼接在s2后面,所得的值就是将s1反转得"dcba",该字符串与s2同有的前缀为"dc",所以值就是2.现在求解在n个字符串给定的情况下,将这些字符串拼接成1个或多个不相交的环所得到的最大值.
思路:建立二分图:左右点集都是1到n个数字,代表1到n的字符串编号. 如果将S[i]连接到S[j]后面能得到x分数,那么就连一条左i与右j的权值为x的边.最终用KM算法求得的最优匹配权值就是可能获得的最大分数
注意:自环的权值为0
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
const int maxn=500+5;
//把W[maxn][maxn]数组的内容读进去之后,调用solve(n)即可计算出二分图最优匹配
//不过需要保证该图肯定有完美匹配
//因为本图用的W[][]来表示一个完全图,所以一定存在完美匹配的
struct Max_Match
{
int W[maxn][maxn],n; //W是权值矩阵,n为左右点集大小
int Lx[maxn],Ly[maxn];//左右点集的可行顶标值
bool S[maxn],T[maxn]; //标记左右点集是否已被访问过
int left[maxn]; //left[i]=j表右i与左j匹配,为-1时表无匹配
bool match(int i)
{
S[i]=true;
for(int j=1;j<=n;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<=n;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;
if(T[i]) Ly[i]+=a;
}
}
int solve(int n)
{
this->n=n;
memset(left,-1,sizeof(left));
for(int i=1;i<=n;i++)//初始化可行顶标值
{
Lx[i]=Ly[i]=0;
for(int j=1;j<=n;j++)
Lx[i]=max(Lx[i], W[i][j]);
}
for(int i=1;i<=n;i++)
{
while(true)
{
for(int j=1;j<=n;j++) S[j]=T[j]=false;
if(match(i)) break;
else update();
}
}
int ans=0;//最优完美匹配的权值
for(int i=1;i<=n;i++) ans+= W[left[i]][i];
return ans;
}
}KM;
int check(string s1,string s2)
{
int ans = 0;
for (int i = 0;i<s1.size()&&i<s2.size();i++)
{
if (s1[i] == s2[s2.size()-1-i])
ans++;
else
return ans;
}
return ans;
}
int main()
{
int n;
while (scanf("%d",&n)!=EOF)
{
string s[maxn];
for (int i = 1;i<=n;i++)
cin >> s[i];
for (int i = 1;i<=n;i++)
for (int j = 1;j<=n;j++)
KM.W[i][j]= i==j ?0:check(s[i],s[j]);
printf("%d\n",KM.solve(n));
}
}