Codeforces 704D Captain America

题目大意

给定一个坐标平面上的 N 个点,要为这些点染色,每种点可以染为两种颜色,红色花费为r,蓝色花费为 b
现在给出m个约束条件,每个条件形如:  ti li di  表示:
1.如果 ti=1 ,那么要求 x=li 上所有点红蓝数量之差小于等于 di
2.如果 ti=2 ,那么要求 y=li 上所有点红蓝数量之差小于等于 di

Data Constraint
N,M100000

题解

假设 r<b (反过来交换就好)
构造一个二分图,每条垂直于X轴的直线作为一个顶点放在二分图左边,每条垂直于Y轴的直线作为一个顶点放在二分图右边。假如有一个点 (x,y) 我们就从左边的 x 向右边的y连一条边,如果这个点选红色我们就把这条边染为1,否则是0。那么一个约束实际上就是要求一个顶点连出去的所有1边和0边之差小于等于 di
不妨设二分图中的一个顶点 i 一共连出去qi条边,所有约束中最小值为 ei ,1边有 ri 条。
易得公式1
公式2
现在在新增一个源点 S 和汇点T
S 向每个x连一条流量为 [Lx,Rx] 的边,每个 y T连一条 [Ly,Ry] 的边。对于没有约束的顶点,我们可以人为地添加一个约束来方便连边。中间那些边的流量就设为1。
如果没有可行流,那么就一定是无解。因为 r<b ,所以我们要最大化红点的数量, S>T 跑一遍最大流就是我们的答案。

时间复杂度: O(M+Nsqrt(N))


上下界网络流

这题用到了上下界网络流,我就顺便写一下上下界网络流的简单处理方法。
新增一个超级源 SS 和一个超级汇 TT 。假设我们现在有一条边 (u,v) ,流量限制为 [L,R] ,我们想让有下界的网络流转化成没有下界的网络流模型,怎么做呢?如果我们能强制将下界那么多的流量流过去就好了。具体连边如下:

  • (SS,v) 流量为 L
  • (u,TT)流量为 R
  • (u,v)流量为 RL
  • (T,S) 流量为 +

这样连边就能保证流量强制流下界的流量。

可行流

SS 出发,到 TT 跑一遍最大流,如果最终 SS 所有的出边都流满了,就说明找到了一个满足下界的可行流。

最大流

求完可行流之后,把辅助建图的 SS TT 以及 (T,S) 都删去,再从 S T跑一遍最大流。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std ;

#define N 100000 + 10
#define M 1000000 + 10
typedef long long ll ;
const int inf = 0x7FFFFFFF ;
struct Point {
    int x , y ;
} P[N] ;

map < int  , int  > from[2];

bool flag = 0 , bz[M] ;
int Node[2*M] , Next[2*M] , C[2*M] , Head[M] , tot = 1 ;
int vh[M] , h[M] , di[M] , Rec[2*M] ;
int Q[M] , E[M] , ans[N] ;
int n , m , r , b ;
int S , T , SS , TT ;
int MaxFlow , num ;

bool cmp( Point a , Point b ) { return a.x < b.x || ( a.x == b.x && a.y < b.y ); }

void link( int u , int v , int w ) {
    Node[++tot] = v , Next[tot] = Head[u] , C[tot] = w , Head[u] = tot ;
    Node[++tot] = u , Next[tot] = Head[v] , C[tot] = 0 , Head[v] = tot ;
}

int SAP( int x , int aug ) {
    int use = 0 ;
    if ( x == T ) return aug ;
    for (int p = di[x] ; p ; p = Next[p] ) {
        if ( h[x] != h[Node[p]] + 1 || C[p] <= 0 || bz[Node[p]] ) continue ;
        di[x] = p ;
        int ret = SAP( Node[p] , min( aug - use , C[p] ) ) ;
        C[p] -= ret ;
        C[p^1] += ret ;
        use += ret ;
        if ( h[S] > num || aug == use ) return use ;
    }
    if ( -- vh[h[x]] == 0 ) { h[S] = num + 1 ; return use ; }
    h[x] ++ ;
    vh[h[x]] ++ ;
    di[x] = Head[x] ;
    return use ;
}

void Flow( int u , int v ) {
    S = u , T = v ;
    memset( h , 0 , sizeof(h) ) ;
    memset( vh , 0 , sizeof(vh) ) ;
    memcpy( di , Head , sizeof(di) ) ;
    vh[0] = num = v + 1 ;
    while ( h[S] <= num ) MaxFlow += SAP( S , inf ) ;
    S = 0 , T = num - 3 ;
}

bool Impossible() {
    for (int p = Head[SS] ; p ; p = Next[p] ) if ( C[p] ) return 1 ;
    return 0 ;
}

int main() {
    scanf( "%d%d" , &n , &m ) ;
    scanf( "%d%d" , &r , &b ) ;
    int Cnt1 = 0 , Cnt2 = 0 ;
    for (int i = 1 ; i <= n ; i ++ ) {
        scanf( "%d%d" , &P[i].x , &P[i].y ) ;
        if ( !from[0][P[i].x] ) from[0][P[i].x] = ++ Cnt1 ;
        if ( !from[1][P[i].y] ) from[1][P[i].y] = ++ Cnt2 ;
        P[i].x = from[0][P[i].x] ;
        P[i].y = from[1][P[i].y] ;
    }
    S = 0 , T = Cnt1 + Cnt2 + 1 ;
    SS = T + 1 , TT = T + 2 , num = TT + 1 ;
    for (int i = 1 ; i <= n ; i ++ ) {
        link( P[i].x , Cnt1 + P[i].y , 1 ) ;
        Q[P[i].x] ++ , Q[Cnt1+P[i].y] ++ ;
        Rec[tot-1] = i ;
    }
    memset( E , 63 , sizeof(E) ) ;
    for (int i = 1 ; i <= m ; i ++ ) {
        int t , l , d ;
        scanf( "%d%d%d" , &t , &l , &d ) ;
        if ( from[t-1].find(l) == from[t-1].end() ) continue ;
        l = from[t-1][l] + (t - 1) * Cnt1 ;
        E[l] = min( E[l] , d ) ;
    }
    for (int i = 1 ; i < T ; i ++ ) {
        E[i] = min( E[i] , Q[i] ) ;
        int L = (Q[i] - E[i]) / 2  + (Q[i] - E[i]) % 2 ;
        int R = (Q[i] + E[i]) / 2 ;
        if ( L > R ) { printf( "-1\n" ) ; return 0 ; }
        if ( i <= Cnt1 ) {
            link( SS , i , L ) ;
            link( S , TT , L ) ;
            link( S , i , R - L ) ;
        } else {
            link( SS , T , L ) ;
            link( i , TT , L ) ;
            link( i , T , R - L ) ;
        }
    }
    link( T , S , inf ) ;
    Flow( SS , TT ) ;
    MaxFlow = C[Head[S]] ;
    if ( Impossible() ) { printf( "-1\n" ) ; return 0 ; }
    Head[S] = Next[Head[S]] , Head[T] = Next[Head[T]] ;
    bz[SS] = bz[TT] = 1 ;
    Flow( S , T ) ;
    if ( r > b ) swap( r , b ) , flag = 1 ;
    printf( "%I64d\n" , (ll)MaxFlow * r + (ll)(n - MaxFlow) * b ) ;
    for (int i = 1 ; i <= Cnt1 ; i ++ ) {
        for (int p = Head[i] ; p ; p = Next[p] ) {
            if ( !Rec[p] ) continue ;
            if ( !C[p] ) ans[Rec[p]] = flag ;
            else ans[Rec[p]] = !flag ;
        }
    }
    for (int i = 1 ; i <= n ; i ++ ) {
        if ( ans[i] == 0 ) printf( "r" ) ;
        else printf( "b" ) ;
    }
    return 0 ;
}

以上.

CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值