[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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章