码迷,mamicode.com
首页 > 其他好文 > 详细

bzoj 2597 [Wc2007]剪刀石头布——费用流

时间:2018-12-14 12:55:41      阅读:160      评论:0      收藏:0      [点我收藏+]

标签:put   void   mem   意义   eof   spfa   name   容量   amp   

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2597

三个人之间的关系,除了“剪刀石头布”,就是有一个人赢了2局;所以考虑算补集,则每个人对答案的贡献是 \( -C_{f[ i ]}^{2} = \frac{f[ i ]*(f[ i ]-1)}{2}\) ,其中 f[ i ] 表示这个人赢的局数。

所以一个人多赢了一局,对答案的贡献是 -f[ i ] ;再多赢一局,就是 -( f[ i ] + 1 ) ……只要每个人向汇点连足够的边,其中每条边容量是1、费用依次为 f[ i ] , f[ i ]+1 , …… 就行了,因为会先走费用小的,符合意义。

对于每场未确定比赛,新建一个点,从源点向它连容量为1、费用为0的边;然后从它分别向两个人连容量为1、费用为0的边,表示这场比赛会令其中一个人增加费用。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=105,M=4955,INF=M;
int n,cnt,f[N],c[N],ans,hd[N+M],xnt=1,b[N][N],dy[M];
int dis[N+M],pre[N+M],incf[N+M];bool vis[N+M];
struct Ed{
  int fr,to,nxt,cap,w;
  Ed(int f=0,int a=0,int b=0,int c=0,int d=0):fr(f),to(a),nxt(b),cap(c),w(d) {}
}ed[(N*N+M*3)<<1];
queue<int> q;
int Mn(int a,int b){return a<b?a:b;}
void add(int x,int y,int z,int w)
{
  ed[++xnt]=Ed(x,y,hd[x],z,w);hd[x]=xnt;
  ed[++xnt]=Ed(y,x,hd[y],0,-w);hd[y]=xnt;
}
bool spfa()
{
  memset(dis,0x3f,sizeof dis);
  dis[0]=0;vis[0]=1;q.push(0);
  pre[cnt]=0;incf[0]=INF;
  while(q.size())
    {
      int k=q.front();q.pop();vis[k]=0;
      for(int i=hd[k],v;i;i=ed[i].nxt)
    if(ed[i].cap&&dis[v=ed[i].to]>dis[k]+ed[i].w)
      {
        dis[v]=dis[k]+ed[i].w;pre[v]=i;
        incf[v]=Mn(incf[k],ed[i].cap);
        if(!vis[v])q.push(v),vis[v]=1;
      }
    }
  return pre[cnt];
}
void ek()
{
  int ret=incf[cnt];
  for(int k=pre[cnt];k;k=pre[ed[k].fr])
    {
      ed[k].cap-=ret;ed[k^1].cap+=ret;
      ans-=ed[k].w*ret;
    }
}
int main()
{
  scanf("%d",&n);cnt=n;int val=0;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
      {
    scanf("%d",&b[i][j]);if(i>=j)continue;
    if(b[i][j]==1)f[i]++; else if(!b[i][j])f[j]++;
    else
      {
        c[i]++;c[j]++;cnt++;val++;
        add(0,cnt,1,0);add(cnt,i,1,0);add(cnt,j,1,0);
        dy[cnt-n]=xnt-1;
      }
      }
  cnt++; ans=n*(n-1)*(n-2)/6;
  for(int i=1;i<=n;i++)
    {
      ans-=f[i]*(f[i]-1)/2;
      for(int j=0;j<c[i];j++)add(i,cnt,1,f[i]+j);
    }
  while(spfa())ek();
  for(int i=1,p=0;i<=n;i++)
    for(int j=1;j<=n;j++)
      {
    if(i>=j||b[i][j]<2)continue;
    p++;
    if(ed[dy[p]].cap)b[i][j]=1,b[j][i]=0;
    else b[i][j]=0,b[j][i]=1;
      }
  printf("%d\n",ans);
  for(int i=1;i<=n;i++,puts(""))
    for(int j=1;j<=n;j++)printf("%d ",b[i][j]);
  return 0;
}

 

bzoj 2597 [Wc2007]剪刀石头布——费用流

标签:put   void   mem   意义   eof   spfa   name   容量   amp   

原文地址:https://www.cnblogs.com/Narh/p/10118256.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!