[BZOJ2034]&[BZOJ4276]-最大收益-线段树优化建边/贪心优化最大权匹配

8 篇文章 0 订阅
4 篇文章 0 订阅

说在前面

并没有什么想说的,但是要保持格式=w=


题目

BZOJ2034传送门
BZOJ4276传送门

题面

给出N件单位时间任务,对于第i件任务,如果要完成该任务,需要占用[Si, Ti]间的某个时刻,且完成后会有Vi的收益。求最大收益。 一个时刻只能做一件任务,做一个任务也只需要一个时刻。
数据规模:N≤5000
BZOJ4276:1 ≤ Si ≤ Ti ≤ 5000,40s时限
BZOJ2034:1 ≤ Si ≤ Ti ≤ 108 10 8 ,1 ≤ Vi ≤ 108 10 8 ,10s时限

输入输出格式

输入格式:
第一行包含一个整数N,含义如题
接下来N行,每行3个整数(s,t,v),含义如题

输出格式:
输出最大的收益


解法

首先看到这道题,应该能反应过来是最大匹配问题。就是让每个任务找一个时间点与之匹配,并获得相应的收益
那么最简单的方式就是:每个任务向区间内所有的时间点连边,然后跑最大权匹配或者费用流。但是复杂度太高了,以至于上面两种数据规模都过不去…

线段树优化建边写法

我们发现,这个连边是有特点的,每个任务向时间点连边都是连续的。于是考虑使用线段树优化建边
首先源点向每个任务连边,容量1,费用为收益。然后每个任务向线段树内的区间连边,容量inf,费用0。最后线段树的每个叶子结点向汇点连边,容量1,费用0。然后跑费用流即可。
这样子就可以通过BZOJ4276

贪心优化最大权匹配

当时间的范围扩大到1e8的时候,即使使用线段树优化建边,线段树的节点也开不下了,于是继续优化算法
1.注意到,假设最后的匹配中不包含u任务,而包含了v任务(u和v的时间区间有交集,并且u的利润大于v),那么一定可以将v换成u,答案会变得更优。于是,我们可以贪心的进行匹配
2.匹配的过程中,肯定是尽量把任务匹配的时间尽可能放到前面去。换句话说,先把所有的任务按照起始时间升序排序,如果u可以匹配上1和2,那么u匹配1不会比匹配2更劣,因为2可能会和后面的点匹配,要把这个机会留给后面的任务。于是,可以把有效的时间点缩到N个。即,对于每个任务u(排序后),找到在u的起始时刻之后第一个没有被占用的时间点,并将之占用。任何一种最优匹配,都可以在把其中的空隙紧缩之后,让所有的匹配点落在这N个占用点上。

由贪心策略,我们把所有的任务按照利益降序排序,并把这些任务在 占用点 上匹配,如果一个任务匹配不上,那么就一定不在最优决策里。而且在匹配u的时候,如果当前时间点now没有被匹配,那么就把u和now匹配上,不然,就让那个结束位置更大的去匹配(即u和match[now]中取结束位置较大的那个),因为如果较大的那个都匹配不上,较小的那个也一定匹配不上,于是这样就省去了遍历所有边的过程,一次匹配复杂度降为 Θ(N) Θ ( N ) 。总复杂度为 Θ(N2) Θ ( N 2 )


下面是自带大常数的代码

BZOJ4276
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , tp = 1 , head[5000*15] , mint = 0x3f3f3f3f , maxt , ans ;
int S , T , id_c , rob[5005] ;
struct Path{
    int pre , to , flow , fee ;
}p[5000*30] ;

void In( int t1 , int t2 , int t3 , int t4 ){
    p[++tp] = ( Path ){ head[t1] , t2 , t3 , t4 } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 , 0 , -t4 } ; head[t2] = tp ;
}

struct Data{
    int a , b , c ;
}d[5005] ;

struct Node{
    int id ;
    Node *ch[2] ;
} *root ;

void newNode( Node *&nd ){
    nd = new Node() ;
    nd->id = ++id_c ;
    nd->ch[0] = nd->ch[1] = NULL ;
}

Node *build( int lf , int rg ){
    Node *nd ; newNode( nd ) ;
    if( lf != rg ){
        int mid = ( lf + rg ) >> 1 ;
        nd->ch[0] = build( lf , mid ) ;
        nd->ch[1] = build( mid+1,rg ) ;
        In( nd->id , nd->ch[0]->id , 1e7 , 0 ) ;
        In( nd->id , nd->ch[1]->id , 1e7 , 0 ) ;
    } else In( nd->id , T , 1 , 0 ) ;
    return nd ;
}

void addE( Node *nd , int lf , int rg , int L , int R , int id ){
    if( L <= lf && rg <= R ){
        In( id , nd->id , 1 , 0 ) ;
        return ;
    } else {
        int mid = ( lf + rg ) >> 1 ;
        if( L <= mid ) addE( nd->ch[0] , lf , mid , L , R , id ) ;
        if( R >  mid ) addE( nd->ch[1] , mid+1,rg , L , R , id ) ;
    }
}

deque<int> que ;
bool inque[5000*15] ;
int dis[5000*15] , pre[5000*15] , preE[5000*15] ;
bool Spfa(){
    memset( pre + 1 , 0 , id_c * sizeof( int ) ) ;
    memset( dis + 1 , 0 , id_c * sizeof( int ) ) ;
    dis[S] = 0 , que.push_front( S ) , inque[S] = true ;
    while( !que.empty() ){
        int u = que.front() ;
        que.pop_front() ; inque[u] = false ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            if( p[i].flow && dis[v] < dis[u] + p[i].fee ){
                dis[v] = dis[u] + p[i].fee ;
                pre[v] = u , preE[v] = i ;
                if( !inque[v] ){
                    if( !que.empty() && dis[v] >= dis[ que.front() ] ) que.push_front( v ) ;
                    else que.push_back( v ) ;
                    inque[v] = true ;
                }
            }
        }
    } return pre[T] ;
}

int addFlow(){
//  puts( " ----------------------- " ) ;
    int flow = 0x3f3f3f3f , rt = 0 , now = T ;
    while( now != S ){
        flow = min( flow , p[ preE[now] ].flow ) ;
        now = pre[now] ;
    } now = T ;
    while( now != S ){
    //  printf( "now = %d\n" , now ) ;
        p[ preE[now] ].flow -= flow ;
        p[ preE[now]^1 ].flow += flow ;
        rt += p[ preE[now] ].fee * flow ;
        now = pre[now] ;
    } return rt ;
}

void solve(){
    while( Spfa() )
        ans += addFlow() ;
    printf( "%d" , ans ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ ) rob[i] = ++id_c ;
    S = ++id_c , T = ++id_c ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%d%d%d" , &d[i].a , &d[i].b , &d[i].c ) ; d[i].b -- ;
        In( S , rob[i] , 1 , d[i].c ) ;
        mint = min( d[i].a , mint ) ;
        maxt = max( d[i].b , maxt ) ;
    }
    root = build( mint , maxt ) ;
    for( int i = 1 ; i <= N ; i ++ )
        addE( root , mint , maxt , d[i].a , d[i].b , rob[i] ) ;
    solve() ;
}
BZOJ2034
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

map<int,int> mp ;
int N , pos[5005] , match[5005] ;
struct Data{
    int st , ed , v ;
}d[5005] ;

bool cmp_st( const Data &A , const Data &B ){ return A.st < B.st ; }
bool cmp_v ( const Data &A , const Data &B ){ return A.v  > B.v  ; }

bool matching( int u , int p ){
    if( p > N || d[u].ed < pos[p] ) return false ;
    if( !match[p] ){
        match[p] = u ;
        return true ;
    } else {
        if( d[ match[p] ].ed < d[u].ed ) return matching( u , p + 1 ) ;
        else if( matching( match[p] , p + 1 ) ){
            match[p] = u ;
            return true ;
        }
    } return false ;
}

void solve(){
    long long ans = 0 ;
    sort( d + 1 , d + N + 1 , cmp_v ) ;
    for( int i = 1 ; i <= N ; i ++ )
        ans += 1LL * matching( i , mp[ d[i].st ] ) * d[i].v ;
    printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%d%d%d" , &d[i].st , &d[i].ed , &d[i].v ) ;
    sort( d + 1 , d + N + 1 , cmp_st ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        pos[i] = max( pos[i-1] + 1 , d[i].st ) ;
        if( !mp.count( pos[i] ) )
            mp[ pos[i] ] = i ;
    }
    solve() ;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值