=== ===
这里放传送门
=== ===
题解
这道题如果考虑直接计算合法的三元组数目好像有点不大科学。。因为只有三场都比完了才能发现这是一个合法组。那么如果考虑不合法情况呢?合法情况肯定是一个有向的三元环,那么不合法情况就是从一个点出发了两条边。也就是说如果有一个人赢了两场比赛,那么就会产生一个不合法三元组。以此类推,如果 有一个人P一共赢了k个人,那么从这k个人里面随便选出两个人来跟P构成的三元组肯定都是不合法的。
那么只需要求出能够产生最少不合法组的情况就可以了。设
C(x)=x(x−1)2
,那么如果一个人P赢了一场比赛,它现在总共赢了
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;
}