不错的题。
ans=n*(n-1)*(n-2)/6-d[i]*(d[i]-1)/2
d[i]表示第i个人胜场次数
d[i]每增加1,ans减少d[i]
源点S向每个点连n-1条容量为1的边,费用分别为0、1、……n-2
每条边对应的点向汇点T连一条容量为1,费用为0的边
每个点向从自己连出的边连一条容量为1,费用为0的边
最小费用最大流
方案数就是看一条边是否满流,满流就是选,否则就是不选
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<iostream> #define maxn 12000 #define maxm 100010 #define inf 1000000000 using namespace std; int head[maxn],c[maxm],len[maxm],p[maxm],next[maxm],to[maxm],fr[maxn],dis[maxn],q[maxn]; bool vis[maxn]; int a[110][110],e[110][110]; int n,m,num,s,t,ans; void addedge(int x,int y,int z,int w) { num++;to[num]=y;c[num]=z;len[num]=w;p[num]=x;next[num]=head[x];head[x]=num; num++;to[num]=x;c[num]=0;len[num]=-w;p[num]=y;next[num]=head[y];head[y]=num; } int cal(int x,int y) { if (x>y) swap(x,y); return (x-1)*n+y; } bool spfa() { for (int i=s;i<=t;i++) dis[i]=inf; int l=0,r=1; dis[s]=0;q[1]=s;vis[s]=1; while (l!=r) { l++;if (l==maxn) l=0; int x=q[l]; for (int p=head[x];p;p=next[p]) if (c[p] && dis[x]+len[p]<dis[to[p]]) { dis[to[p]]=dis[x]+len[p]; fr[to[p]]=p; if (!vis[to[p]]) { r++;if (r==maxn) r=0; q[r]=to[p];vis[to[p]]=1; } } vis[x]=0; } if (dis[t]==inf) return 0; else return 1; } void mcf() { int x=inf; for (int i=fr[t];i;i=fr[p[i]]) x=min(x,c[i]); for (int i=fr[t];i;i=fr[p[i]]) ans-=x*len[i],c[i]-=x,c[i^1]+=x; } void costflow() { while (spfa()) mcf(); } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%d",&a[i][j]); ans=n*(n-1)*(n-2)/6; s=0;t=n*(n+1)+1;num=1; for (int i=1;i<=n;i++) for (int j=0;j<n;j++) addedge(s,i,1,j); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { if (a[i][j]==1 || a[i][j]==2) addedge(i,n+cal(i,j),1,0),e[i][j]=num-1; if (i<j) addedge(n+cal(i,j),t,1,0); } costflow(); printf("%d\n",ans); for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { if (j!=1) printf(" "); if (a[i][j]!=2) printf("%d",a[i][j]); else if (c[e[i][j]]) printf("0"); else printf("1"); } printf("\n"); } return 0; }