题目: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 ",ans); for(int i=1;i<=n;i++,puts("")) for(int j=1;j<=n;j++)printf("%d ",b[i][j]); return 0; }