【bzoj 2597】剪刀石头布(费用流)

传送门biu~
一张竞赛图中,任意选三个点如果不形成环,那么一定有一个点的出度为2。也就是每当在任意一个点中找到任意两个出度,图中就会失去一个三元环。
令点 i i 的出度为degreei,则对于整张图来说,三元环的个数为 C3nΣC2degreei C n 3 − Σ C d e g r e e i 2 。问题就变成了在边数恒定时如何使 ΣC2degreei Σ C d e g r e e i 2 最小。可以看出,每当一个点 i i degreei加一,就会减少 degreei1 d e g r e e i − 1 个三元环。所以可以把每条边用一个点来表示,以出度为流量,构建最小费用最大流模型:
源点S向每个原图中的节点连 n1 n − 1 条弧,流量均为 1 1 。费用分别为012....n2,代表着每次这个节点连出一条出边(即 degreei d e g r e e i ++)时所减少的三元环数量。
每个原图中的节点向自己的出边连一条流量为1,费用为 0 0 的弧。
每条边向汇点 T T 连一条流量为1,费用为 0 0 的弧。
求最小费用,再用Cn3减去即为所求的最多三元环的数量。求方案时只需要判断原图中边的辅助点是在哪个方向被流过的即可。

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int n,S,T,cost,a[105][105],Edge[105][105],dis[10105];
int head[10105],fir[10105],nex[60005],to[60005],cap[60005],val[60005],tp=1;
bool b[10105];
inline int calc(int x,int y){if(x>y)swap(x,y);return x*n+y;}
inline void add(int x,int y,int c,int v){
    nex[++tp]=head[x];
    head[x]=tp;
    to[tp]=y;
    cap[tp]=c;
    val[tp]=v;
}
inline void Insert(int x,int y,int c,int v){add(x,y,c,v);add(y,x,0,-v);}
inline bool spfa(){
    for(int i=S;i<=T;++i)   dis[i]=inf,b[i]=0;
    dis[S]=0;queue<int>q;q.push(S);
    while(!q.empty()){
        int x=q.front();q.pop();b[x]=false;
        for(int i=head[x];i;i=nex[i]){
            if(cap[i] && dis[x]+val[i]<dis[to[i]]){
                dis[to[i]]=dis[x]+val[i];
                if(!b[to[i]])   q.push(to[i]),b[to[i]]=true;
            }
        }
    }
    return dis[T]^inf;
}
int dfs(int x,int now){
    if(x==T || now==0){
        cost+=now*dis[T];
        return now;
    }
    int c=0;b[x]=true;
    for(int &i=fir[x];i;i=nex[i]){
        if(!b[to[i]] && cap[i] && dis[to[i]]==dis[x]+val[i]){
            int f=dfs(to[i],min(now,cap[i]));
            now-=f;
            cap[i]-=f;
            cap[i^1]+=f;
            c+=f;
            if(!now)    break;
        }
    }
    return c;
}
inline void Dinic(){
    while(spfa()){
        for(int i=S;i<=T;++i)   fir[i]=head[i];
        dfs(S,inf);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            scanf("%d",&a[i][j]);
    S=0;T=n+n*n+1;
    for(int i=1;i<=n;++i)
        for(int j=0;j<=n-2;++j)
            Insert(S,i,1,j);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(a[i][j]) Insert(i,calc(i,j),1,0),Edge[i][j]=tp;
            if(i<j) Insert(calc(i,j),T,1,0);
        }
    }
    Dinic();
    printf("%d\n",n*(n-1)*(n-2)/6-cost);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(a[i][j]^2)   printf("%d",a[i][j]);
            else            printf("%d",cap[Edge[i][j]]);
            printf("%c",j==n?'\n':' ');
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zP1nG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值