Codeforces Educational Round#21 F(808F) Solution:网络流(最小割)

题意:给出一组卡牌(100张),卡牌有三个属性值:power,c,level,其中c和level是用来限制的,power是目标值。

具体的限制规则是:只有level小于等于玩家的playerlevel的卡牌才可以使用,任意两张c之和为prime的卡牌不能同时使用。

求出一个人物的最小等级,使得可以找到一个方案使得选用的卡牌的power值累计大于等于k。


这个题很容易想到二分playerlevel,然后关键在于已知playerlevel的时候,如何计算可以得到的最大的power。

Solution是使用maxFlow,这算是一个网络流建图题。

首先可以预处理出所有相互冲突的卡牌。如果在他们之间连接一条边,就构成了一个冲突网络。显而易见:一条边的两个端点一定是一个奇数一个偶数。

那么可以把节点归为偶数的点和奇数的点。现在的问题是,要去掉一些点,使得网络为空。下面讲解建图技巧。


构造一个偶数超源102点,一个奇数超汇101点,102点会和每一个偶数点连一条指向偶数点的边,边的容量是偶数点的power,101点会和每个技术点连一条指向101点的边,容量为奇数点的power。然后再冲突的奇偶点之间连接从偶数点指向奇数点的边,容量是INF。建图完成。


那么首先这张初始图上的总power值就是各个奇偶点的power之和,然后下面开始“去掉一些点,使得不存在冲突关系”。


从101到102跑一次maxFlow可以得到这个冲突网络的最大流,也就是最小割。那么考虑这个最小割的实际意义:划定一个边集,这个边集可以将冲突网络分成不想连的两部分,且我这个边集的边权之和最小,那么显然,我们的那些冲突边不可能出现在这里边, 因为他们都是INF,因此,显而易见,这个最小割当然就是我们要得到的答案。因为割掉这些边之后,我们就不存在一条经过102-偶数点-冲突边-奇数点-101的流了,也就是说,要么奇数点-101的边被割了,要么偶数点-102的边被割了,要么同时被割了(可能存在多重冲突情况,需要两个点都割),这样就能保证网络变得合法而不冲突,而且最小割也保证了这是代价最小的割,因此就是我们要的答案。


格外要注意1的情形,1虽然属于奇数点,但是考虑出现多个1的时候,这些1都是“奇数”,但是任意两个1之间也存在冲突关系,但是上面我们只考虑了从超源102-超汇101之间要没有带冲突的流,而没有考虑1-1之间这个冲突边,所以不能同时出现多个1在我们的图中,这个也很好处理,显然,在我们的卡组中,至多会有一张c=1的牌,所以建图的时候,先遍历一次得到我可以使用的power最大的那个c=1的牌,而把剩下其他的c=1的牌都舍弃掉就可以了。


注:dinic不需要反向弧


AC代码(需要改动):

#include<bits/stdc++.h>
using namespace std;
#define MAXN 105
#define MAXM 100005
int first[MAXN],nxt[MAXM],dis[MAXM],cont[MAXM];
int m,n,k,s,t,tot;
int p[MAXN],c[MAXN],l[MAXN];
bool prm[200005];
int deep[MAXN];
int que[MAXN*10];
bool hasEdge[MAXN][MAXN];
int maxone=-1;
int maxoneindex=-1;
bool bfs(){
    int l=0,r=1;
    que[1]=s;
    memset(deep,0,sizeof(deep));
    while (l<r){
        l++;
        int q = que[l];
        int tt=first[q];
        while (tt!=-1){
            if (dis[tt]!=s&&cont[tt]>0&&deep[dis[tt]]==0){
                deep[dis[tt]]=deep[q]+1;
                r++;
                que[r]=dis[tt];
            }
            tt=nxt[tt];
        }
    }
    if (deep[t]==0){
        return false;
    }else{
    
        return true;
    }
}
int dfs(int now,int limit){
    if (now==t||limit==0){
        return limit;
    }
    if (limit<=0){
        return 0;
    }
    int flow=0,f;
    int tt=first[now];
    while (tt!=-1){
        if (deep[dis[tt]]-deep[now]==1){
            int temp = dfs(dis[tt],min(limit,cont[tt]));
            cont[tt]-=temp;
            cont[tt^1]+=temp;
            limit-=temp;
            flow+=temp;
            if (limit==0){
                break;
            }
        }
        tt=nxt[tt];
    }
    return flow;
}
int maxFlow(int least){
    tot=-1;
    for (int i=0;i<103;i++){
        first[i]=-1;
    }
    for (int i=0;i<n;i++){
        for (int j=i+1;j<n;j++){
            if (c[i]==1&&i!=maxoneindex){
                continue;
            }
            if (c[j]==1&&j!=maxoneindex){
                continue;
            }
            if (hasEdge[i][j]&&l[i]<=least&&l[j]<=least){
                int ii,jj;
                if (c[i]&1){
                    ii=i;
                    jj=j;
                }else{
                    ii=j;
                    jj=i;
                }
                tot++;
                cont[tot]=2147483647;
                dis[tot]=jj;
                nxt[tot]=first[ii];
                first[ii]=tot;
                tot++;
                cont[tot]=0;
                dis[tot]=ii;
                nxt[tot]=first[jj];
                first[jj]=tot;
            }
        }
    }
    int sum =0;
    for (int i=0;i<n;i++){
        if (c[i]==1&&i!=maxoneindex){
            continue;
        }
        if (l[i]<=least){
            sum+=p[i];
            if (c[i]&1){
                tot++;
                cont[tot]=p[i];
                dis[tot]=i;
                nxt[tot]=first[101];
                first[101]=tot;
                tot++;
                cont[tot]=p[i];
                dis[tot]=101;
                nxt[tot]=first[i];
                first[i]=tot;
            }else{
                tot++;
                cont[tot]=p[i];
                dis[tot]=i;
                nxt[tot]=first[102];
                first[102]=tot;
                tot++;
                cont[tot]=p[i];
                dis[tot]=102;
                nxt[tot]=first[i];
                first[i]=tot;
            }
        }
    }
    int ans =0;
    while (bfs()){
        ans+=dfs(s,2147483647);
    }
    return sum-ans;
} 
int main(){
    memset(prm,true,sizeof(prm));
    prm[0]=prm[1]=false;
    for (int i=2;i<200000;i++){
        if (prm[i]){
            int tmp=i<<1;
            while (tmp<200000){
                prm[tmp]=false;
                tmp+=i;
            }
        }
    }
    cin>>n>>k;
    memset(hasEdge,false,sizeof(hasEdge));
    for (int i=0;i<n;i++){
        cin>>p[i]>>c[i]>>l[i];
        if (c[i]==1&&p[i]>maxone){
            maxone=p[i];
            maxoneindex=i;
        }
        for (int j=0;j<i;j++){
            if (prm[c[i]+c[j]]){
                hasEdge[i][j]=hasEdge[j][i]=true;
            }
        }
    }
    s=101;
    t=102;
        if (maxFlow(101)<k){
        cout<<"-1"<<endl;
        return 0;
    }
    int l=0;
    int r=101;
    while (r-l>1){
        int mid =(l+r)>>1;
        if (maxFlow(mid)>=k){
            r=mid;
        }else{
            l=mid;
        }
    }
    if (maxFlow(l)>=k){
        cout<<l<<endl;
    }else{
        cout<<r<<endl;
    } 
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值