[Luogu P4249] [BZOJ 2597] [WC2007]剪刀石头布

洛谷传送门
BZOJ传送门

题目描述

在一些一对一游戏的比赛(如下棋、乒乓球和羽毛球的单打)中,我们经常会遇到 A A 胜过 B B B 胜过 C C C 又胜过 A 的有趣情况,不妨形象的称之为剪刀石头布情况。有的时候,无聊的人们会津津乐道于统计有多少这样的剪刀石头布情况发生,即有多少对无序三元组 (A,B,C) ( A , B , C ) ,满足其中的一个人在比赛中赢了另一个人,另一个人赢了第三个人而第三个人又胜过了第一个人。注意这里无序的意思是说三元组中元素的顺序并不重要,将 (A,B,C) ( A , B , C ) (A,C,B) ( A , C , B ) (B,A,C) ( B , A , C ) (B,C,A) ( B , C , A ) (C,A,B) ( C , A , B ) (C,B,A) ( C , B , A ) 视为相同的情况。

N N 个人参加一场这样的游戏的比赛,赛程规定任意两个人之间都要进行一场比赛:这样总共有 N(N1)/2 场比赛。比赛已经进行了一部分,我们想知道在极端情况下,比赛结束后最多会发生多少剪刀石头布情况。即给出已经发生的比赛结果,而你可以任意安排剩下的比赛的结果,以得到尽量多的剪刀石头布情况。

输入输出格式

输入格式:

输入文件的第 1 1 行是一个整数 N ,表示参加比赛的人数。

之后是一个 N N N 列的数字矩阵:一共 N N 行,每行 N 列,数字间用空格隔开。

在第 (i+1) ( i + 1 ) 行的第 j j 列的数字如果是 1 ,则表示 i i 在已经发生的比赛中赢了 j ;该数字若是 0 0 ,则表示在已经发生的比赛中 i 败于 j j ;该数字是 2 ,表示 i i j 之间的比赛尚未发生。数字矩阵对角线上的数字,即第 (i+1) ( i + 1 ) 行第 i i 列的数字都是 0 ,它们仅仅是占位符号,没有任何意义。

输入文件保证合法,不会发生矛盾,当 ij i ≠ j 时,第 (i+1) ( i + 1 ) 行第 j j 列和第 (j+1) 行第 i i 列的两个数字要么都是 2 ,要么一个是 0 0 一个是 1

输出格式:

输出文件的第 1 1 行是一个整数,表示在你安排的比赛结果中,出现了多少剪刀石头布情况。

输出文件的第 2 行开始有一个和输入文件中格式相同的 N N N 列的数字矩阵。第 (i+1) ( i + 1 ) 行第 j j 个数字描述了 i j j 之间的比赛结果, 1 表示 i i 赢了 j 0 0 表示 i 负于 j j ,与输入矩阵不同的是,在这个矩阵中没有表示比赛尚未进行的数字 2 ;对角线上的数字都是 0 0 。输出矩阵要保证合法,不能发生矛盾。

输入输出样例

输入样例#1:

3
0 1 2
0 0 2
2 2 0

输出样例#1:

1
0 1 0
0 0 1
1 0 0

说明

【评分标准】

对于每个测试点,仅当你的程序的输出第一行的数字和标准答案一致,且给出了一个与之一致的合法方案,你才能得到该测试点的满分,否则该测试点得 0 分。

【数据范围】

30 %的数据中, N6 N ≤ 6

100 %的数据中, N100 N ≤ 100

解题分析

首先, 我们可以知道总的选择方案数为 (N3)=N×(N1)×(N2)6 ( N 3 ) = N × ( N − 1 ) × ( N − 2 ) 6

不妨将 A A 赢过B看做一条由 A A 连向B的有向边。设 degA d e g A 为点 A A 的入度, 那么减少的方案数就为(degA2)(因为随便两个赢过 A A 的人和A都不可能再出现贡献, 而且由于我们这样的统计方式, 并不会重复计算)。这样我们想要尽量减少减少的方案数, 与最小费用最大流的思想契合。

这样就好办了。 我们要做的是其实就是给无向边定向, 给无向边的其中一个点加上 1 1 的入度。 进一步发现, (12)=0,(22)=1,(32)=3,(42)=6 于是我们从每个点向汇点连出费用为 1,2,3, 1 , 2 , 3 , ⋯ ,流量为 1 1 的边, 表示入度为2,3,4,的情况下的减少的方案数;将每条边视为一个点, 从源点向其连费用为0,流量为1的边,向边两端连费用为0,流量为1的边, 表示入度只能加在其中一个点上。注意原来本来定了向的边不用处理。

最后输出方案的时候看哪条边流量为0即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <queue>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 20050
#define base 105
#define S 0
#define T 20000
#define INF 100000000
template <class TT>
IN void in(TT &x)
{
    x = 0; R char c = gc;
    for (; !isdigit(c); c = gc);
    for (;  isdigit(c); c = gc)
    x = (x << 1) + (x << 3) + c - 48;
}
struct Edge{int to, fl, cost, nex;} edge[MX << 4];
int head[MX], layer[MX], dis[MX], del[MX], pre[MX],
mp[105][105], rec[105][105], deg[MX];
bool inq[MX];
int dot, cnt = -1, ans;
IN void add(R int from, R int to, R int fl, R int cost)
{
    edge[++cnt] = {to, fl, cost, head[from]}, head[from] = cnt;
    edge[++cnt] = {from, 0, -cost, head[to]}, head[to] = cnt;
}
IN int getid(R int x, R int y) {return (x - 1) * dot + y + base;}
namespace MCMF
{
    std::queue <int> q;
    IN bool SPFA()
    {
        std::memset(dis, 63, sizeof(dis));
        dis[S] = 0; del[S] = INF; q.push(S); R int now;
        W (!q.empty())
        {
            now = q.front(); q.pop();
            for (R int i = head[now]; ~i; i = edge[i].nex)
            {
                if(edge[i].fl > 0 && dis[edge[i].to] > dis[now] + edge[i].cost)
                {
                    dis[edge[i].to] = dis[now] + edge[i].cost;
                    del[edge[i].to] = std::min(edge[i].fl, del[now]);
                    pre[edge[i].to] = i;
                    if(!inq[edge[i].to]) q.push(edge[i].to), inq[edge[i].to] = true;
                }
            }
            inq[now] = false;
        }
        return dis[T] < INF;
    }
    IN void updata()
    {
        ans -= del[T] * dis[T];
        R int now = T, pr;
        W (now != S)
        {
            pr = pre[now];
            edge[pr].fl -= del[T];
            edge[pr ^ 1].fl += del[T];
            now = edge[pr ^ 1].to;
        }
    }
    IN void init() {W (SPFA()) updata();}
}
int main(void)
{
    std::memset(head, -1, sizeof(head));
    in(dot); int buf; ans = (dot - 2) * (dot - 1) * dot / 6;
    for (R int i = 1; i <= dot; ++i)
    for (R int j = 1; j <= dot; ++j)
    {in(mp[i][j]); if(mp[i][j] == 1) ++deg[j];}
    for (R int i = 1; i < dot; ++i)
    for (R int j = i + 1; j <= dot; ++j)
    {
        if(mp[i][j] == 2)
        {
            buf = getid(i, j);
            add(S, buf, 1, 0);
            add(buf, i, 1, 0);
            rec[j][i] = cnt - 1;
            add(buf, j, 1, 0);
            rec[i][j] = cnt - 1;//预存边的编号
        }
    }
    for (R int i = 1; i <= dot; ++i)
    {
        ans -= deg[i] * (deg[i] - 1) / 2;
        for (R int j = deg[i] + 1; j < dot; ++j) add(i, T, 1, j - 1);
    }
    MCMF::init();
    printf("%d\n", ans);
    for (R int i = 1; i <= dot; ++i)
    {
        for (R int j = 1; j <= dot; ++j)
        {
            if(mp[i][j] < 2) printf("%d", mp[i][j]);
            else printf("%d", edge[rec[j][i]].fl);
            if(j < dot) putchar(32);
        }
        puts("");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值