bzoj2597 [Wc2007]剪刀石头布(费用流)

题目链接

分析:
题目要求给未知边定向,使得图中有最多个三元环

一道看似和网络流完全没有关系的题,不过之前我们接触过一道图上问题用网络流解决的问题(无向边定向变欧拉路)
而这道题,我们也可以强扯到网络流上

我们的原则:正难则反

显然,整个图能产生的最多三元环有 C(n,3) C ( n , 3 ) 个,我们需要将不合法的子图减掉(补集法)
如果三个点无法形成环,那么ta一定长这样:
这里写图片描述
一个点有两个出度,一个点有两个入度,一个点有出度入度各一个
我们考虑有两个出度的这个点,设ta的出度为 degree[i] d e g r e e [ i ]
那么与ta相关的不合法子图就有 C(degree[i],2) C ( d e g r e e [ i ] , 2 )

ans=C(n,3)i=1nC(degree[i],2)=n(n1)(n2)6i=1nC(degree[i],2) a n s = C ( n , 3 ) − ∑ i = 1 n C ( d e g r e e [ i ] , 2 ) = n ( n − 1 ) ( n − 2 ) 6 − ∑ i = 1 n C ( d e g r e e [ i ] , 2 )

显然我们要使 ni=1C(degree[i],2) ∑ i = 1 n C ( d e g r e e [ i ] , 2 ) 尽量小(符合最小割)

C(degree[i],2)=degree[i]2degree[i]2=1+2+3++(degree[i]1) C ( d e g r e e [ i ] , 2 ) = d e g r e e [ i ] 2 − d e g r e e [ i ] 2 = 1 + 2 + 3 + … + ( d e g r e e [ i ] − 1 )

那么我们就可以拆边使得费用( degree[i]2degree[i]2 d e g r e e [ i ] 2 − d e g r e e [ i ] 2 )和流量( degree[i] d e g r e e [ i ] )的关系符合上式
下面我们就可以用费用流计算每个点的 degree d e g r e e ,同时最小化花费(最小费用最大流)
对于每场比赛,已知的比赛就没有什么悬念了,而未知的比赛获胜者非此即彼,因此我们可以建图:
这里写图片描述

tip

同一场比赛不要重复建图
对于确定获胜的比赛,我们记录一下直接统计到 degree[i]2degree[i]2 ∑ d e g r e e [ i ] 2 − d e g r e e [ i ] 2 中,就不要加入图中了

一T:边数太多,精简建图
二WA:发现是SPJ,就这样吧

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int INF=0x33333333;
const int N=15000;
const int M=50010;
struct node{
    int x,y,v,c,nxt;
};
node way[M];
int st[N],tot=-1,n,s,t,pre[N],dis[N],ans[102][102],w[102],Ans=0;
bool in[N];

void add(int u,int w,int z,int cc) {
    tot++;way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].c=cc;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].c=-cc;way[tot].nxt=st[w];st[w]=tot;
}

int spfa(int s,int t) {
    memset(in,0,sizeof(in));
    memset(dis,0x33,sizeof(dis));
    queue<int> q;
    q.push(s); dis[s]=0; in[s]=1;
    while (!q.empty()) {
        int now=q.front(); q.pop();
        in[s]=0;
        for (int i=st[now];i!=-1;i=way[i].nxt)
            if (way[i].v&&dis[way[i].y]>dis[now]+way[i].c) {
                dis[way[i].y]=dis[now]+way[i].c;
                pre[way[i].y]=i;
                if (!in[way[i].y]) {
                    in[way[i].y]=1;
                    q.push(way[i].y);
                }
            }
    }
    return dis[t]!=INF;
}

void solve() {
    while (spfa(s,t))
    {
        int sum=INF;
        for (int i=t;i!=s;i=way[pre[i]].x)
            sum=min(sum,way[pre[i]].v);
        Ans=Ans+sum*dis[t];
        for (int i=t;i!=s;i=way[pre[i]].x)
            way[pre[i]].v-=sum,
            way[pre[i]^1].v+=sum;
    }

    Ans=n*(n-1)*(n-2)/6-Ans;
    for (int i=1;i<=tot;i++) 
        if (way[i].x>n&&way[i].x<t&&way[i].y!=s&&!way[i].v){
            int tmp=way[i].x-n;
            int x=(tmp-1)/n+1,y=tmp-(x-1)*n;
            if (way[i].y==y) swap(y,x);
            ans[x][y]=1,ans[y][x]=0;
        }

    printf("%d\n",Ans);
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++)
            printf("%d ",ans[i][j]);
        printf("\n");
    }
}

int main()
{
    memset(st,-1,sizeof(st));
    scanf("%d",&n);
    s=0,t=n*n+n+1;

    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) {
            int num=(i-1)*n+j+n;
            int x; scanf("%d",&x);
            if (x==1) w[i]++,ans[i][j]=1;       //确定获胜 
            else if (x==2&&i<j) {               //同一场比赛不要重复添加 
                add(s,num,1,0);
                add(num,i,1,0);
                add(num,j,1,0);
            }
        }
    for (int i=1;i<=n;i++) {
        Ans+=w[i]*(w[i]-1)/2;
        for (int j=w[i];j<n;j++)
            add(i,t,1,j);
    }

    solve();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值