【BZOJ 1486】 [HNOI2009]最小圈

1486: [HNOI2009]最小圈

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 1112   Solved: 532
[ Submit][ Status]

Description

【BZOJ 1486】 [HNOI2009]最小圈_第1张图片 【BZOJ 1486】 [HNOI2009]最小圈_第2张图片


01分数规划问题+spfa判断负环。


二分一个答案ans,将图中的每条边的权值都减去ans,如果存在负环,说明ans还可以更小;否则ans应当更大。


但是直接用spfa判断负环,对于每一个ans最慢是O(|V||E|),会TLE。


那么我们可以直接dfs判负环,每次只走使d[y]减小的路,当走到一个点发现他已经被访问过,则一定存在负环。


#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <queue>
using namespace std;
int n,m,tot,in[3005],v[3005],h[3005],f;
double d[3005],l,r;
struct edge
{
	int y,ne;
	double v;
}e[10005];
void Addedge(int x,int y,double v)
{
	tot++;
	e[tot].y=y;
	e[tot].ne=h[x];
	e[tot].v=v;
	h[x]=tot;
}
void dfs(int x,double k)
{
	in[x]=1;
	for (int i=h[x];i;i=e[i].ne)
	{
		int y=e[i].y;
		if (d[y]>d[x]+e[i].v-k)
		{
			if (in[y]) {f=0;return;}
			d[y]=d[x]+e[i].v-k;
			dfs(y,k);
			if (!f) return;
		}
	}
	in[x]=0;
}
bool judge(double k)
{
	f=1;
	for (int i=1;i<=n;i++)
	{
		dfs(i,k);
		if (!f) return false;
	}
	return true;
}
void Solve()
{
	double ans;
	while (r-l>1e-10)
	{
		double m=(l+r)/(double)2;
		for (int i=1;i<=n;i++)
		   in[i]=0,d[i]=0.0;		
                if (!judge(m)) r=m;
		else ans=l,l=m;
	}
	printf("%.8lf\n",ans);
}
double maxx(double a,double b)
{
	if (a<b) return b;
	return a;
}
double minn(double a,double b)
{
	if (a<b) return a;
	return b;
}
int main()
{
	//freopen("in.in","r",stdin);freopen("out.out","w",stdout);
        scanf("%d%d",&n,&m);
	r=(double)-1e7;
	l=(double)1e7;
	for (int i=1;i<=m;i++)
	{
		int x,y;
		double w;
		scanf("%d%d%lf",&x,&y,&w);
		Addedge(x,y,w);
		r=maxx(r,w),l=minn(l,w);
	}
	Solve();
	return 0;
}



感悟:

1.改成dfs还TLE是因为并没有完全理解dfs这种做法,d[]的初始值都设成了inf,实际上这样比spfa还要慢。


d[]的初始值都设成0,遇到使他为负的才继续走,大大减少了递归层数。


为什么一定能找到负环?


我们假设存在负环,但没有找到负环,则这个环一定可以分成若干段>=0的部分(只有这样才会停止dfs),但是这与负环的权值和<0矛盾。


因此,假设不成立。

你可能感兴趣的:(OI,bzoj,01分数规划)