题意:给出一些单词,通过某种规则可以规定两个单词相连所能获得的权值,将这些单词形成若干个不相交的环,使得权值最大。
思路:可以转化成一个完全图,选择若干条边,使得图变成若干个不相交的环,使得,边权和最大。很容易可以看出每一个点都有一个前驱和一个后继,这样就很容想到最优匹配,KM直接求得答案。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define N 201
#define M 1006
bool xm[N], ym[N];
int w[N][N], A[N], B[N], rm[N];
int ms;
char s[N][M];
bool path(int a)//寻找可增广路
{
int b;
xm[a] = 1;//标记 Xi 在交错树中
for (b = 1;b <= ms;b++)
{
if (!ym[b] && A[a] + B[b] == w[a][b])//是否有 A[i] + B[j] = W[i][j]
{
ym[b] = 1;//标记 Yj 在交错树中
if (rm[b] == 0 || path(rm[b]))//j未被匹配 或者 可以找到可增广路
{
rm[b] = a;//匹配
return 1;
}
}
}
return 0;
}
void KM()
{
int i, j, sum = 0, d, k;
memset (B, 0, sizeof (B));
for (i = 1;i <= ms;i++)
{
A[i] = -INF;
for (j = 1;j <= ms;j++)
{
A[i] = max(A[i], w[i][j]);
}
}
memset (rm, 0, sizeof (rm));
for (i = 1;i <= ms;i++)
{
while (1)
{
memset (xm, 0, sizeof (xm));
memset (ym, 0, sizeof (ym));
if (path(i))
break;
d = INF;
for (k = 1;k <= ms;k++)
{
if (xm[k])
{
for (j = 1;j <= ms;j++)
{
if (!ym[j])
d = min(d, A[k] + B[j] - w[k][j]);
}
}
}
for (j = 1;j <= ms;j++)
{
if (xm[j])
A[j] -= d;
if (ym[j])
B[j] += d;
}
}
}
for (i = 1;i <= ms;i++)
sum += w[rm[i]][i];
printf ("%d\n", sum);
}
int cal(int x, int y) {
int i, j, lx = strlen(s[x]), ly = strlen(s[y]);
for (i = 0, j = 0;i < lx && j < ly;i++, j++) {
if (s[x][lx-1-i] != s[y][j]) break;
}
return min(i, j);
}
int main()
{
int n, i, j;
while (~scanf("%d", &n)) {
for (i = 1;i <= n;i++) {
scanf("%s", s[i]);
}
for (i = 1;i <= n;i++) {
for (j = 1;j <= n;j++) w[i][j] = -INF;
w[i][i] = 0;
}
ms = n;
for (i = 1;i <= n;i++) {
for (j = 1;j <= n;j++) {
if (i == j) continue;
w[i][j] = cal(i, j);
}
}
KM();
}
}