题解 P4249 【[WC2007]剪刀石头布】

Imagine

2018-03-06 21:36:52

Solution

## 题目大意 竞赛图中的部分边方向已确定,你需要决定剩余边的方向,使得整个图上的三元环数量最多。 ## 题解 从整个图的 $n$个点中任取 $3$个点,方案数为$$C^{3}_{n}=\frac{n!}{3!(n-3)!}=\frac{n(n-1)(n-2)}{6}$$ 考虑 $3$个点不构成三元环的情况,必然为一点的入度为 $2$,一点的出度为 $2$,一点的入度出度为 $1$。不妨从入度入手,一个点若入度为 $2$,则表明失去了一个三元环,若入度为 $3$,则会失去 $3$个三元环(考虑点 $A$被点 $B, C, D$通过边指向,那么 $(A,B,C), (A,B,D), (A,C,D)$都不会是三元环),因此,对于一个点 $u$而言,记其入度为 $degree(u)$,那么点 $u$会使整张图会失去 $C^{2}_{degree(u)}$个三元环。故对于最终答案 $ans$,有 $$ans = \frac{n(n-1)(n-2)}{6} - \sum_{u \in V}{C^{2}_{degree(u)}}$$ 通过上述过程已经能够发现,对于一个点 $u$,考虑差分,若其入度增加 $1$,则会使整个图失去的三元环个数为 $$C^{2}_{degree(u)} - C^{2}_{degree(u)-1} = degree(u)-1$$因此,可以将失去的三元环作费用,跑费用流。 我们对于未定向的边,将其视为点(以下称作「边对应的点」),建图如下: $1.$ 由源点 $s$向每一条边对应的点连边,容量为 $1$,费用为 $0$; $2.$ 由每一条边对应的点向该边连接的两个图上结点连边,容量为 $1$,费用为 $0$,表明该条边只会使得其中一个点入度 $+1$; $3.$ 由每一个图上结点向汇点 $t$连若干条边,费用依次为 $0, 1, 2, 3......$(分别表示该点入度从 $0$开始每增加 $1$就会失去的三元环数量),容量均为 $1$(注意初始图的处理,即某些点初始入度不为 $0$)。 最后的输出方案,对于未定向边,只需看费用流的图中,边对应的点指向的两个图上结点的边中哪一条满流即可。 ## 代码 ```cpp #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <queue> using namespace std; const int maxn = 5150 + 10; const int maxg = 100 + 10; const int INF = 1000000000; int n; int s, t; int rel[maxg][maxg]; int wedge[maxg][maxg]; int indeg[maxn]; int edgeidx(int x, int y) { return (2*n-x) * (x-1) / 2 + (y-x) + n; } struct Edge { int from, to, cap, flow, cost; Edge(int from, int to, int cap, int flow, int cost): from(from), to(to), cap(cap), flow(flow), cost(cost) {} }; template <int maxn> struct MCMF { int n, m, s, t; vector <Edge> edges; vector <int> G[maxn]; int inq[maxn]; int d[maxn]; int p[maxn]; int a[maxn]; void init(int n) { this->n = n; for(int i = 0; i < n; i++) G[i].clear(); edges.clear(); } void AddEdge(int from, int to, int cap, int cost) { edges.push_back(Edge(from, to, cap, 0, cost)); edges.push_back(Edge(to, from, 0, 0, -cost)); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BellmanFord(int s, int t, int& cost) { for(int i = 0; i < n; i++) d[i] = INF; memset(inq, 0, sizeof(inq)); d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF; queue <int> Q; Q.push(s); while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = 0; for(int i = 0; i < G[u].size(); i++) { Edge& e = edges[G[u][i]]; if(e.cap > e.flow && d[e.to] > d[u] + e.cost) { d[e.to] = d[u] + e.cost; p[e.to] = G[u][i]; a[e.to] = min(a[u], e.cap - e.flow); if(!inq[e.to]) { Q.push(e.to); inq[e.to] = 1; } } } } if(d[t] == INF) return false; cost += d[t] * a[t]; int u = t; while(u != s) { edges[p[u]].flow += a[t]; edges[p[u]^1].flow -= a[t]; u = edges[p[u]].from; } return true; } int Mincost(int s, int t) { int cost = 0; while(BellmanFord(s, t, cost)); return cost; } }; MCMF <maxn> solver; int main() { scanf("%d", &n); s = 0; t = (n+1)*n/2 + n + 1; solver.init(t+1); for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) { scanf("%d", &rel[i][j]); if(rel[i][j] == 1) indeg[j]++; } for(int i = 1; i <= n; i++) for(int j = i+1; j <= n; j++) { if(rel[i][j] < 2) continue; solver.AddEdge(s, edgeidx(i, j), 1, 0); solver.AddEdge(edgeidx(i, j), i, 1, 0); wedge[j][i] = solver.m - 2; solver.AddEdge(edgeidx(i, j), j, 1, 0); wedge[i][j] = solver.m - 2; } int down = 0; for(int i = 1; i <= n; i++) { down += indeg[i] * (indeg[i]-1) / 2; for(int j = indeg[i] + 1; j < n; j++) solver.AddEdge(i, t, 1, j-1); } down += solver.Mincost(s, t); printf("%d\n", n*(n-1)*(n-2) / 6 - down); for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { if(rel[i][j] < 2) printf("%d", rel[i][j]); else printf("%d", solver.edges[wedge[i][j]].flow); if(j < n) printf(" "); } printf("\n"); } return 0; } ```