HDU 3722 Card Game(二分图最优匹配)

题意:给出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));
	}
}

Description

Jimmy invents an interesting card game. There are N cards, each of which contains a string Si. Jimmy wants to stick them into several circles, and each card belongs to one circle exactly. When sticking two cards, Jimmy will get a score. The score of sticking two cards is the longest common prefix of the second card and the reverse of the first card. For example, if Jimmy sticks the card S1 containing "abcd" in front of the card S2 containing "dcab", the score is 2. And if Jimmy sticks S2 in front of S1, the score is 0. The card can also stick to itself to form a self-circle, whose score is 0. 

For example, there are 3 cards, whose strings are S1="ab", S2="bcc", S3="ccb". There are 6 possible sticking: 
1.  S1->S2, S2->S3, S3->S1, the score is 1+3+0 = 4 
2.  S1->S2, S2->S1, S3->S3, the score is 1+0+0 = 1 
3.  S1->S3, S3->S1, S2->S2, the score is 0+0+0 = 0 
4.  S1->S3, S3->S2, S2->S1, the score is 0+3+0 = 3 
5.  S1->S1, S2->S2, S3->S3, the score is 0+0+0 = 0 
6.  S1->S1, S2->S3, S3->S2, the score is 0+3+3 = 6 
So the best score is 6. 

Given the information of all the cards, please help Jimmy find the best possible score. 

 

Input

There are several test cases. The first line of each test case contains an integer N (1 <= N <= 200). Each of the next N lines contains a string Si. You can assume the strings contain alphabets ('a'-'z', 'A'-'Z') only, and the length of every string is no more than 1000. 

 

Output

Output one line for each test case, indicating the corresponding answer.
 

Sample Input

       
       
3 ab bcc ccb 1 abcd
 

Sample Output

       
       
6 0
 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值