【WC2007】石头剪刀布

题面

先放链接:https://www.luogu.org/problemnew/show/P4249
简述题意:一个n个点的竞赛图,给定其中一些边的方向,确定剩余边的方向使得图中的三元环数量尽量多

分析

这个题想了好久啊qwq,想的全是错的建图…最后还是看题解了才搞明白
我们发现构成三元环这个条件比较难以考虑,所以考虑反过来:什么情况下会无法构成三元环。任意三个点无法构成三元环,一定是其中一个点出去两个边,一个点进去两个边,一个点一进一出。那么考虑入度,当一个点有一个入度的时候,没有问题;当一个点有两个入度的时候,会有一个三点组无法构成三元环;进而当一个点有k个入度的时候,会有C(2k)个三点组无法构成三元环。每增加一个入度,会使得之前的每个入度都可以与他组成一个使这个点有两个入度的三点组,即减少之前的入度个数的三元环。注意算了入度就不要再算出度了,因为边(u,v)会是u的出度,也会是v的入度,不要重复计算。
如此我们的思路就比较清晰了,对于一条边(u,v),一定会使得uv的入度增加1,于是我们把每个边作为一个点,s连它,容量1费用0,每个边连向两个顶点,容量为1费用为0,表示只会使得其中一个增加入度。每个点ut,费用分别为DegreeuDegreeu+1Degreeu+2Degreeu+3……对全图跑一次最小费用最大流,用原本的答案减去即为答案。原本的答案为n个点选出3个,即C(3n)。同时对入度不是0的点u要先减去1+2+3..+(Degreeu1)

可以借鉴的内容

  1. 网络流与其他算法(如图论)的结合,先用其他做法转化问题,再用网络流求解
  2. 正着不会就反过来,不会求三元环就求有多少个不是三元环

代码

#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;
}
posted @ 2018-07-01 17:31  hychyc1  阅读(87)  评论(0编辑  收藏  举报