POJ--2924[Word Rings] (二分+SPFA判正环)

 

题目描述:
给你n个字符串。
如果一个字符串的最后两个字符和另一个字符串的开头两个字符相同的话,那么这两个字符串可以连接。
现在让你连接成一个环(当然,每个字符串只能用一次)。
这个环的平均长度是:环中所有的字符串长度之和 除以 字符串的个数。
输出 最长 的平均长度。
如果不存在,输出 无解。
 
解题报告:
建图:
单词:intercommunicational
那么就连接in节点 和 al节点,权值为单词的长度。
问题就转化成了:
在图中找环,环的边权相加 除以 边数 就是这个环的平均长度。
 


但是怎样找到最长的呢?
二分!
注意到:
枚举每一个平均长度mid值。
如果存在一个环,(E1+...+Ek)/k>=mid(其中k是边数,E1……Ek是各个边权),
那么正解比mid大,否则比mid小,这就是二分策略。
那么怎样知道是否存在(E1+...+Ek)/k>=mid 呢?
如下转化:(E1+...+Ek)>=mid*k
E1-mid + E2–mid + E3-mid + ... + Ek-mid >= 0
所以,把所有的边权改为Ei – mid,然后看是否存在正环就可以,存在就是满足条件。

 

 

源代码:

/*二分+SPFA判正环(有向图)*/
/*
(1):例intercommunicational则把in->al权值为20
*/
#include <iostream>
#include <cstdio>
#include <memory.h>
#include <algorithm>
#include <cstring>
#include <cmath>
#define MAXN 700
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define INF 1e8
#define eps 1e-4
const int vn=26*26;
using namespace std;
struct edge
{
	int u,v,w,next;
}E[2000000];
int Q[20000000];
int head[MAXN],ecnt;
double dis[MAXN];
bool vis[MAXN];
bool mark[MAXN];
int num[MAXN];
int N,Max,Min,scr;
int map[MAXN][MAXN];
void Insert(int u,int v,int w)
{
	E[ecnt].u=u;
	E[ecnt].v=v;
	E[ecnt].w=w;
	E[ecnt].next=head[u];
	head[u]=ecnt++;
}
void Init()
{
	int i,j,u,v,w;
	char s[1005];
	memset(head,-1,sizeof(head));ecnt=0;
	memset(map,0,sizeof(map));
	for(i=1;i<=N;i++)
	{
		scanf("%s",s);
		w=strlen(s);
		if(w<2) continue;
		Max=max(Max,w);
		Min=min(Min,w);
		u=(s[0]-'a')*26+(s[1]-'a')+1;
		v=(s[w-2]-'a')*26+(s[w-1]-'a')+1;
		map[u][v]=max(map[u][v],w);//优化
	}
	for(i=1;i<=vn;i++)
	{
		for(j=1;j<=vn;j++)
		{
			if(map[i][j])
				Insert(i,j,map[i][j]);
		}
	}
	//关键,建一个超级源点保证原图联通(类似查分约束)
	scr=0;
	for(i=1;i<=vn;i++)
		Insert(scr,i,0);
}
bool SPFA(int s,double mid)
{
	int i,u,v;
	double w;
	memset(num,0,sizeof(num));
	memset(vis,false,sizeof(vis));
	for(i=1;i<=vn;i++)
		dis[i]=-INF;
	int Tail,Head;
	Tail=Head=0;
	Q[Tail++]=s;
	vis[s]=true;
	dis[s]=0;
	num[s]=1;
	while(Tail!=Head)
	{
		u=Q[Head++];
		vis[u]=false;
		for(i=head[u];i!=-1;i=E[i].next)
		{
			v=E[i].v;
			w=(double)E[i].w-mid;
			if(dis[v]<dis[u]+w)
			{
				dis[v]=dis[u]+w;
				if(!vis[v])
				{
					vis[v]=true;
					num[v]++;
					if(num[v]>=vn)//有正环
						return true;
					Q[Tail++]=v;
				}
			}
		}
	}
	return false;
}
bool Judge(double mid)
{
	int i,u,v;
	return SPFA(scr,mid);
}
void Solve()
{
	double l,r,mid;
	l=(double)(Min);
	r=(double)(Max);
	if(!Judge(0))
	{printf("No solution.\n");return;}
	while(fabs(l-r)>eps)
	{
		mid=(l+r)/2.0;
		if(Judge(mid))
			l=mid;
		else
			r=mid;
	}
	printf("%.2lf\n",l);  
}
int main()
{
	while(scanf("%d",&N),N)
	{
		Init();
		Solve();
	}
	return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__简言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值