题面
先放链接:https://www.luogu.org/problemnew/show/P4249
简述题意:一个n个点的竞赛图,给定其中一些边的方向,确定剩余边的方向使得图中的三元环数量尽量多
分析
这个题想了好久啊qwq,想的全是错的建图…最后还是看题解了才搞明白
我们发现构成三元环这个条件比较难以考虑,所以考虑反过来:什么情况下会无法构成三元环。任意三个点无法构成三元环,一定是其中一个点出去两个边,一个点进去两个边,一个点一进一出。那么考虑入度,当一个点有一个入度的时候,没有问题;当一个点有两个入度的时候,会有一个三点组无法构成三元环;进而当一个点有
个入度的时候,会有
个三点组无法构成三元环。每增加一个入度,会使得之前的每个入度都可以与他组成一个使这个点有两个入度的三点组,即减少之前的入度个数的三元环。注意算了入度就不要再算出度了,因为边
会是
的出度,也会是
的入度,不要重复计算。
如此我们的思路就比较清晰了,对于一条边
,一定会使得
或
的入度增加
,于是我们把每个边作为一个点,
连它,容量
费用
,每个边连向两个顶点,容量为
费用为
,表示只会使得其中一个增加入度。每个点
连
,费用分别为
,
,
,
……对全图跑一次最小费用最大流,用原本的答案减去即为答案。原本的答案为
个点选出
个,即
。同时对入度不是
的点
要先减去
。
可以借鉴的内容
- 网络流与其他算法(如图论)的结合,先用其他做法转化问题,再用网络流求解
- 正着不会就反过来,不会求三元环就求有多少个不是三元环
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#define ri register int
#define MAXN 105
using namespace std;
int n, win[MAXN][MAXN], in[MAXN], cnt, tot, s, t, MAXF, MINC, q[MAXN*1000], w[MAXN*1000], c[MAXN*1000], inq[MAXN*1000], ans;
struct node{
int v, c, w; node *next, *rev;
}pool[MAXN*1000], *h[MAXN*1000], *pre[MAXN*1000];
inline void addedge(int u, int v, int c, int w) {
node *p = &pool[++cnt], *q = &pool[++cnt];
*p = node{v, c, w, h[u], q}, h[u] = p;
*q = node{u, 0, -w, h[v], p}, h[v] = q;
}
bool SPFA() {
int front = 0, rear = 1;
memset(w, 127, sizeof(w));
memset(c, 0, sizeof(c));
memset(inq, 0, sizeof(inq));
int INF = w[0];
q[0] = w[0] = 0, inq[0] = 1, c[0] = INF;
while(front < rear) {
int u = q[front++];
inq[u]--;
for(node *p = h[u]; p; p = p->next) {
if(w[p->v] > w[u] + p->w && p->c) {
pre[p->v] = p;
c[p->v] = min(c[u], p->c);
w[p->v] = w[u] + p->w;
if(!inq[p->v]) q[rear++] = p->v, inq[p->v]++;
}
}
}
if(w[t] == INF) return 0;
MINC += w[t]*c[t], MAXF += c[t];
int u = t;
while(u != s) {
pre[u]->c -= c[t];
pre[u]->rev->c += c[t];
u = pre[u]->rev->v;
}
return 1;
}
int main(){
scanf("%d", &n);
ans = n*(n-1)*(n-2)/6;
tot = n;
for(ri i = 1; i <= n; ++i) {
for(ri j = 1; j <= n; ++j) {
scanf("%d", &win[i][j]);
if(i >= j) continue;
//i win j === i->j
if(win[i][j] == 1) ans -= in[j]++;
else if(win[i][j] == 0) ans -= in[i]++;
}
}
for(ri i = 1; i <= n; ++i) {
for(ri j = 1; j <= n; ++j) {
if(i >= j) continue;
//i win j === i->j
if(win[i][j] == 2)
addedge(++tot, i, 1, 0), addedge(tot, j, 1, 0);
}
}
t = n+tot+1;
for(ri i = n+1; i <= tot; ++i) addedge(s, i, 1, 0);
for(ri i = 1; i <= n; ++i)
for(ri j = 1; j <= n; ++j)
addedge(i, t, 1, in[i]++);
while(SPFA());
int x[2];
for(ri i = n+1; i <= tot; ++i) {
for(node *p = h[i]; p; p = p->next) x[p->c] = p->v; // x1 wins
win[x[1]][x[0]] = 1, win[x[0]][x[1]] = 0;
}
printf("%d\n", ans - MINC);
for(ri i = 1; i <= n; ++i) {
for(ri j = 1; j <= n; ++j) {
printf("%d ", win[i][j]);
}
printf("\n");
}
return 0;
}