【HDU】4406 GPA 最大费用流

传送门:【HDU】4406 GPA


题目分析:今天郁闷死啊。。。M取小了竟然一直没查出来T ^ T。。。而且数据真恶心,用的还是四舍五入(我linux下是五凑偶的),让我半天纠结了精度导致没发现M的问题。。

输入得到所有课程的信任度wi[ i ]。sum = sum{ 1600 * wi[ i ] }。

建边形如( 弧头 , 弧尾 , 容量 , 费用 )。

由于随着大于等于60分以后每多得到一分,GPA就会增大,所以我们就需要得到的费用最大,将费用取反就是使得到的费用最小。接下来的建边费用都是取反了的。

首先所有的科目如果有小于60分的,建边( i , t , 60 - score , -M )(M要比能得到的最大分数还大,取1e7就好了),表示这部分的分必须先拿到,need += 60 - score(need表示将让所有科目不挂科至少需要的分数)。然后建边( i , t , 1 , wi[ i ] * 3 * ( 2 * j + 1 - 2 * 40 ) ),0 <= j < 40 。

否则如果有科目i的分数score大于等于60分的,ans += wi[ i ] * ( 4 * 1600 - 3 * ( 100 - score ) * ( 100 - score ) ),同时建边( i , t , 1 , wi[ i ] * 3 * ( 2 * j + 1 - 2 * ( 100 - score ) ),score - 60 <= j < 40。

最后对所有的天i建边( s , i + m , K , 0 ) , 所有天向该天能复习的科目建边( i + m , j , K , 0 )。

跑一遍最小费用最大流,如果(-cost)/M不等于need则无解输出0.000000,否则输出ans/sum/1600。


代码如下:


#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define FOR( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define REV( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; -- i )
#define travel( e , H , u ) for ( Edge* e = H[u] ; e ; e = e -> next )
#define CLR( a , x ) memset ( a , x , sizeof a )
#define cPY( a , x ) memcpy ( a , x , sizeof a )

typedef long long LL ;

const int M = 1e7 ;//3 * 1600 * 40 * 20 = 3840000 < 1e7
const int MAXN = 65 ;
const int MAXE = 10000 ;
const LL INF = 1e15 ;

struct Edge {
	int v , c , w , n ;
	Edge () {}
	Edge ( int v , int c , int w , int n ) : v ( v ) , c ( c ) , w ( w ) , n ( n ) {}
} ;

struct Net {
	Edge E[MAXE] ;
	int H[MAXN] , cntE ;
	int cap[MAXN] , cur[MAXN] ;
	LL d[MAXN] ;
	int Q[MAXN] , head , tail ;
	bool vis[MAXN] ;
	int s , t ;
	int flow ;
	LL cost ;
	int n , m , K ;
	int wi[MAXN] ;
	void init () {
		cntE = 0 ;
		CLR ( H , -1 ) ;
	}
	void addedge ( int u , int v , int c , int w ) {
		E[cntE] = Edge ( v , c ,  w , H[u] ) ;
		H[u] = cntE ++ ;
		E[cntE] = Edge ( u , 0 , -w , H[v] ) ;
		H[v] = cntE ++ ;
	}
	bool spfa () {
		REP ( i , 0 , MAXN ) d[i] = INF ;
		CLR ( vis , 0 ) ;
		head = tail = 0 ;
		cap[s] = M ;
		cur[s] = -1 ;
		d[s] = 0 ;
		Q[tail ++] = s ;
		while ( head != tail ) {
			int u = Q[head ++] ;
			if ( head == MAXN ) head = 0 ;
			vis[u] = 0 ;
			for ( int i = H[u] ; ~i ; i = E[i].n ) {
				int v = E[i].v , c = E[i].c , w = E[i].w ;
				if ( c && d[v] > d[u] + w ) {
					d[v] = d[u] + w ;
					cap[v] = min ( c , cap[u] ) ;
					cur[v] = i ;
					if ( !vis[v] ) {
						vis[v] = 1 ;
						//if ( d[v] < d[Q[head]] ) {
						//	if ( head == 0 ) head = MAXN ;
						//	Q[-- head] = v ;
						//} else {
							Q[tail ++] = v ;
							if ( tail == MAXN ) tail = 0 ;
						//}
					}
				}
			}
		}
		if ( d[t] == INF ) return 0 ;
		flow += cap[t] ;
		cost += cap[t] * d[t] ;
		for ( int i = cur[t] ; ~i ; i = cur[E[i ^ 1].v] ) {
			E[i].c -= cap[t] ;
			E[i ^ 1].c += cap[t] ;
		}
		return 1 ;
	}
	int MCMF () {
		flow = cost = 0 ;
		while ( spfa () ) ;
		return cost ;
	}
	void scanf ( int& x , char c = 0 ) {
		while ( ( c = getchar () ) < '0' || c > '9' ) ;
		x = c - '0' ;
		while ( ( c = getchar () ) >= '0' && c <= '9' ) x = x * 10 + c - '0' ;
	}
	void solve () {
		int c , score ;
		int ans = 0 , sum = 0 ;
		int need = 0 ;
		init () ;
		s = 0 ;
		t = n + m + 1 ;
		FOR ( i , 1 , m ) {
			scanf ( wi[i] ) ;
			sum += 1600 * wi[i] ;
		}
		FOR ( i , 1 , m ) {
			scanf ( score ) ;
			if ( score < 60 ) {
				addedge ( i , t , 60 - score , -M ) ;//M一开始只取了1e6,wa到死啊。。。
				need += 60 - score ;
				score = 60 ;
			}
			ans += wi[i] * ( 4 * 1600 - 3 * ( 100 - score ) * ( 100 - score ) ) ;
			int x = 100 - score ;
			REP ( j , 0 , x ) addedge ( i , t , 1 , wi[i] * 3 * ( j * 2 + 1 - x * 2 ) ) ;
		}
		FOR ( i , 1 , n ) {
			addedge ( s , m + i , K , 0 ) ;
			FOR ( j , 1 , m ) {
				scanf ( c ) ;
				if ( c ) addedge ( m + i , j , K , 0 ) ;
			}
		}
		MCMF () ;
		if ( ( -cost ) / M != need ) printf ( "%.6f\n" , 0.0 ) ;
		else {
			ans += ( -cost ) % M ;
			printf ( "%.6f\n" , ( double ) ans / sum ) ;
		}
	}
} e ;

int main () {
	while ( ~scanf ( "%d%d%d" , &e.n , &e.K , &e.m ) && ( e.n || e.K || e.m ) ) e.solve () ;
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值