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

=== ===

这里放传送门

=== ===

题解

这道题如果考虑直接计算合法的三元组数目好像有点不大科学。。因为只有三场都比完了才能发现这是一个合法组。那么如果考虑不合法情况呢?合法情况肯定是一个有向的三元环,那么不合法情况就是从一个点出发了两条边。也就是说如果有一个人赢了两场比赛,那么就会产生一个不合法三元组。以此类推,如果 有一个人P一共赢了k个人,那么从这k个人里面随便选出两个人来跟P构成的三元组肯定都是不合法的。

那么只需要求出能够产生最少不合法组的情况就可以了。设C(x)=x(x1)2 ,那么如果一个人P赢了一场比赛,它现在总共赢了v 场比赛,那么第v 场比赛的贡献就是C(v)C(v1) 。那么考虑用费用流来模拟这个过程,为每场比赛建立一排点,从S连到它们,流量为1费用为0;每个比赛向它涉及到的两个人连流量为1费用为0的边,表示这场比赛要么A赢要么B赢。然后每个人再向T连很多边,每条边的费用都是C(v)C(v1) ,流量为1。这样的话它会自动选费用小的边先走,就相当于模拟了v的递增。

一开始感觉边好像会很多然后写了个动态加边。。然而好像不用动态加边也是可以的。对于构造方案的话,可以发现每一场比赛连出去的那两条边的流量情况就代表了比赛的输赢,可以直接访问这些边得出合法方案。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1000000000
#define inc(x)(x=(x==11000)?1:x+1)
using namespace std;
int n,a[110][110],p[11010],dis[11010],q[11010],head,tail,pre[11010],tot,S,T,Cost;
int sum[110],cnt,num[110][110],rec[110][110];
bool ext[11010];
struct edge{
    int to,flw,cst,nxt,id;
}e[500010];
void add(int from,int to,int flow,int cost,int idf){
    e[tot].to=to;e[tot].flw=flow;e[tot].cst=cost;
    e[tot].nxt=p[from];e[tot].id=idf;p[from]=tot++;
}
int calc(int v){return v*(v-1)/2;}
void change(int now){
    int v=e[now].id,val;
    e[now].id=e[now^1].id=0;
    ++sum[v];val=calc(sum[v]+1)-calc(sum[v]);
    add(v+cnt,T,1,val,v);//动态加边
    add(T,v+cnt,0,-val,-v);
}
int Increase(int S,int T){
    int Min=inf;
    for (int i=T;i!=S;i=e[pre[i]^1].to)
      Min=min(Min,e[pre[i]].flw);
    for (int i=T;i!=S;i=e[pre[i]^1].to){
        e[pre[i]].flw-=Min;
        e[pre[i]^1].flw+=Min;
        if (e[pre[i]].id>0)
          change(pre[i]);
    }
    return Min;
}
bool SPFA(int &Cost){
    int delta;
    memset(dis,127,sizeof(dis));
    head=0;tail=1;q[tail]=S;
    ext[S]=true;dis[S]=0;
    while (head!=tail){
        int u;
        inc(head);u=q[head];ext[u]=false;
        for (int i=p[u];i!=-1;i=e[i].nxt)
          if (e[i].flw>0&&dis[e[i].to]>dis[u]+e[i].cst){
              int v=e[i].to;pre[v]=i;
              dis[v]=dis[u]+e[i].cst;
              if (ext[v]==false){
                  inc(tail);q[tail]=v;ext[v]=true;  
              }
          }
    }
    if (dis[T]>inf) return false;
    delta=Increase(S,T);
    Cost+=delta*dis[T];
    return true;
}
int main()
{
    memset(p,-1,sizeof(p));
    scanf("%d",&n);S=0;
    for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++){
          scanf("%d",&a[i][j]);
          if (a[i][j]==1) ++sum[i];
          if (a[i][j]==2){
              if (i<=j){
                  num[i][j]=++cnt;
                  add(S,cnt,1,0,0);
                  add(cnt,S,0,0,0);
              }
          }
      }
    T=cnt+n+1;
    for (int i=1;i<=n;i++){
        int val=calc(sum[i]);
        Cost+=val;//记录已经存在的不合法三元组
        val=calc(sum[i]+1)-val;//计算增量
        add(i+cnt,T,1,val,i);
        add(T,i+cnt,0,-val,-i);
    }
    memset(rec,-1,sizeof(rec));
    for (int i=1;i<=n;i++)
      for (int j=i;j<=n;j++)
        if (a[i][j]==2){
            int now=num[i][j];
            rec[i][j]=tot;//记录每一场比赛对应的边的编号
            add(now,i+cnt,1,0,0);add(i+cnt,now,0,0,0);
            add(now,j+cnt,1,0,0);add(j+cnt,now,0,0,0);
        }
    while (SPFA(Cost));
    Cost=n*(n-1)*(n-2)/6-Cost;
    printf("%d\n",Cost);
    for (int i=1;i<=n;i++)
      for (int j=i;j<=n;j++){
          int v=rec[i][j];
          if (v==-1) continue;
          if (e[v].flw==0){a[i][j]=1;a[j][i]=0;}
          else{a[i][j]=0;a[j][i]=1;}
      }
    for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++)
        printf("%d%c",a[i][j]," \n"[j==n]);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章