Gym 102055B Balance of the Force (二分图染色 + 联通块 + 线段树)

题意

给定 n 个物品,该物品放在 A ,B阵营 有不同的价值,ai,bi。现在需要将 每个物品选择一个阵营,使得整体 n 个物品价值的最大值和最小值之差最小。同时有 m 个限制,每个限制 为 (xi , yi )表示 xi 号物品 和 yi号物品不能放在同一阵营。如果加入该限制后无法有一种合理的安排就是输出 IMPOSSIBLE,否则输出最大值和最小值之差的最小值。
( 1<=n,m<=2e5, 1<=ai,bi<=1e9,1<=xi,yi<=n)

思路

首先用染色把二分图判了,然后跟西安邀请赛一个题目类似,由于一个二分图,对于一个点确定,那么整个连通图的阵营情况就明了了,单个点也可以看作一个二分图,因此,对于每个联通图去求出他的两种二元组(mi,ma),(西安那个题然后转化为了背包问题,这里则是数据结构)。然后问题抽象为,给定 block 个元素,每个元素可以有两个二元组,必须选择其中一个,求一种选择方式使得最大值最小值之差最小。
然后我考虑贪心,动归无果… 感觉没办法只能枚举最小值或者最大值,然后就陷入了不知咋求未被枚举的量的最优值。模拟赛的时候我就陷入了二分的僵局,虽然觉得二分不太对,但也没发现错在哪,求懂的巨巨可以评论解释一下。
补题才知道… 这个模型主要麻烦的点在于每个元素有两个二元组,会有一个选择问题。当不知道怎么做的时候排个序总是好的,起码可以解决一维的偏序。然后将所有元素的两个二元组全都按右端点从小到大排序,每次遇到遇到一个二元组,将其 mi 插入线段树,如果其同一个元素的对应二元组已经存在了,由于我们枚举最大值,对于同一个元素的二元组我们肯定选择mi较大的那个,因此去更新对应位置的最小值,取大,但是整个线段树是维护的整体最小值。那何时可以更新答案呢,当已经出现了所有的联通块的时候就可以更新答案。因为之前没有把图全覆盖的时候更新答案不合法,也没意义。所以要用一个 blkcnt 记录已经出现的联通块的个数。

然后看到这里可能有一个疑问,会不会存在 一个元素同时选择了两个二元组来更新了答案呢? 实际上是不可能的。设 排在前面的一个二元组为 (x1,y1) 后一个二元组为 (x2,y2) ,因为按最大值排序可知, y1 <= y2,这里设 y1 < y2,如果出现那种选了两个更新答案的情况,即存在 x1 > x2,且 x1 是目前的全局最小值。然后分类讨论:

  1. 如果 (x1,y1) 的时候连通块已经全覆盖,那么如果在后面出现(x2,y2),那因为 y2 > y1 所以肯定不如 y1-x1的答案优。(即使这两个二元组相邻出现)
  2. 如果(x1,y1)的时候连通块还没全覆盖,也就是还未统计答案。然后(x2,y2)出现的时候连通块已经全覆盖,那么因为这两个来自同一个联通块,所以他们不可能连续出现,不然那连通块个数没有增加,(x2,y2)出现的时候仍然不必统计答案,设中间夹着一个(x3,y3),那么可知 y3<=y3,所 y3-x1 <= y2-x1 ,所以不会出现。
代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
#define maxm 1006
#define ll long long int
#define INF 0x3f3f3f3f
#define inc(i,l,r) for(int i=l;i<=r;i++)
#define dec(i,r,l) for(int i=r;i>=l;i--)
#define mem(a) memset(a,0,sizeof(a))
#define sqr(x) (x*x)
#define inf (ll)2e18+1
#define PI acos(-1)
#define ls x<<1
#define rs x<<1|1
#define mod 1000000007
#define auto(i,x) for(int i=head[x];i;i=ed[i].nxt)
ll read(){
    ll x=0,f=1ll;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
     while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
     return f*x;
}
int T,n,m;
struct node{int v,nxt;}ed[maxn<<1];
int head[maxn],tot;
void add(int x,int y){ed[++tot]={y,head[x]};head[x]=tot; }
int a[maxn][2];
int col[maxn];
struct nbnb{int mi,ma,id;
bool operator < (const nbnb & rid)const{
    return ma < rid.ma;
}
}seg[maxn<<1];
int cnt;
bool dfs(int x,int y,int c){/// c是颜色 0,1 初始要把col搞成-1
    col[x]=c;
    bool flag=0;
    for(int i=head[x];i;i=ed[i].nxt){
        int v=ed[i].v;
        if(v==y)continue;
        if(col[v]>=0){
            if(col[v]^col[x]==0)flag=1;
        }
        else if(dfs(v,x,c^1))flag=1;
    }
    return flag;
}
bool vis[maxn][2];
void giao(int x,int c,int typ,int num){
    vis[x][typ]=1;
    seg[num].mi=min(seg[num].mi,a[x][c]);
    seg[num].ma=max(seg[num].ma,a[x][c]);
    for(int i=head[x];i;i=ed[i].nxt){
        int v=ed[i].v;
        if(vis[v][typ])continue;
        giao(v,c^1,typ,num);
    }
}
int t[maxn<<2];
void pushup(int x){t[x]=min(t[ls],t[rs]);}
void build(int x,int l,int r){
    t[x]=INF;
    if(l==r){return ;}
    int mid=(l+r)>>1;
    build(ls,l,mid);build(rs,mid+1,r);
}
void update(int x,int l,int r,int pos,int val){
    if(l==r){
        if(t[x]==INF)t[x]=val;
        else t[x]=max(t[x],val);
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)update(ls,l,mid,pos,val);
    else update(rs,mid+1,r,pos,val);
    pushup(x);
}
bool blkvis[maxn];
int blkcnt;
int main()
{
    T=read();
    int kase=0;
    while(T--){
        n=read();m=read();
        inc(i,0,n)vis[i][0]=vis[i][1]=head[i]=0;
        tot=0;
        int x,y;
        inc(i,1,m){
            x=read();y=read();
            add(x,y);add(y,x);
        }
        inc(i,1,n){
            a[i][0]=read();a[i][1]=read();
        }
        printf("Case %d: ",++kase);
        bool flag=0;
        int block=cnt=0;
        inc(i,0,n)col[i]=-1;
        inc(i,1,n)if(col[i]==-1){flag|=dfs(i,0,1);block++;}
        inc(i,1,block*2){seg[i].ma=0;seg[i].mi=INF; }
        inc(i,0,block)blkvis[i]=0;
        blkcnt=0;
        if(flag)printf("IMPOSSIBLE\n");
        else {
            inc(i,1,n){
                if(vis[i][0]==0){
                    giao(i,0,0,++cnt);
                    giao(i,1,1,++cnt);
                    seg[cnt-1].id=cnt/2;
                    seg[cnt].id=cnt/2;
                }
            }
            sort(seg+1,seg+cnt+1);
            build(1,1,block);
            int ans=INF;
            inc(i,1,cnt){
                if(blkvis[seg[i].id]==0){
                    blkcnt++;
                    blkvis[seg[i].id]=1;
                }
                update(1,1,block,seg[i].id,seg[i].mi);
                if(blkcnt==block)ans=min(ans,seg[i].ma-t[1]);
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值