[USACO07DEC]观光奶牛Sightseeing Cows(分数规划+判定负环)

传送门

双倍经验 代码差不多

题意:n个点,m条有向边,每个点有一个乐趣值val,经过每条边花费时间wi,求从某一个点出发最后又回到出发点,使平均每单位时间内获得的乐趣值最大.

分析:设环S={V,E}(V是点集,E是边集).理解题意,我们要求的实际上就是\(\frac{\sum_{i=1}^{k}val[v_i]}{\sum_{i=1}^{k}w[e_i]}\)

\(x=\frac{\sum_{i=1}^{k}val[v_i]}{\sum_{i=1}^{k}w[e_i]}\)

整理得\(\sum_{i=1}^kval[v_i]-x*\sum_{i=1}^{k}w[e_i]=0\)

继续得\(\sum_{i=1}^k(val[v_i]-x*w[e_i])=0\)

\(f(x)=\sum_{i=1}^k(val[v_i]-x*w[e_i])\)

f(x)随x增大而减小,即f(x)是一个递减函数,既然具有单调性,就要想到二分答案.

我们直接二分要求的答案x,check时把每条边的边权看作\(val[v_i]-x*w[e_i]\)如果此次二分的值合法,即\(\frac{\sum_{i=1}^kval[v_i]}{\sum_{i=1}^{k}w[e_i]}>x\),根据上面的推导得\(\sum_{i=1}^k(val[v_i]-x*w[e_i])>0\),即图中存在一个正环.

我们一般都是求负环吧,所以我们稍稍转换一下,把\(\sum_{i=1}^k(val[v_i]-x*w[e_i])>0\)两边同时乘上-1,变成了\(\sum_{i=1}^k(-val[v_i]+x*w[e_i])<0\)

所以本题就转换为了二分x,把每条边权看作\(-val[v_i]+x*w[e_i]\),然后判断图中是否存在负环.

我写的是dfs+spfa判负环(如果不会判负环,请看广告).

注意本题是实数域上的二分答案.

int n,m,tot,visit[1005],val[1005];
int head[1005],nxt[5005],to[5005],w[5005];
double eps=1e-4,dis[1005];
void add(int a,int b,int c){
    nxt[++tot]=head[a];
    head[a]=tot;
    to[tot]=b;
    w[tot]=c;
}
bool dfs_spfa(int x,double mid){
    visit[x]=1;
    for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(dis[y]>dis[x]-val[x]+mid*w[i]){
	    	dis[y]=dis[x]-val[x]+mid*w[i];
	    	if(visit[y]||dfs_spfa(y,mid))
        		return 1;
		}
    }
    visit[x]=0;
    return 0;
}
bool check(double mid){
    memset(visit,0,sizeof(visit));
    for(int i=1;i<=n;i++)dis[i]=0;
    for(int i=1;i<=n;i++)
		if(dfs_spfa(i,mid))return 1;
    return 0;
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)val[i]=read();
    for(int i=1;i<=m;i++){
		int a=read(),b=read(),c=read();
		add(a,b,c);
    }
    double l=-1e9,r=1e9,mid;
    while(l+eps<r){
		mid=(l+r)/2.0;
		if(check(mid))l=mid;
		else r=mid;
    }
    printf("%.2lf\n",l);
    return 0;
}

posted on 2019-02-12 22:07  PPXppx  阅读(116)  评论(0编辑  收藏  举报