2022“杭电杯”中国大学生算法设计超级联赛(10)Winner Prediction(最大流dinic)

题意为给n个人,给出已完成和未完成的m1和m2场比赛

每场比赛赢的人加一分,输的人不扣分,已完成的会给出结果,问第一个人可不可以获胜

积分最搞的一个人或一群人都获胜

(赛场上贪了无数发,不出意外全寄了)

首先贪心的想,已完成比赛的分数我们无法确定,但未完成比赛我们可以选择让谁获胜

有1的比赛肯定让1获胜,这样全部进行完之后,1的分数就确定了

如果这个时候有人分数超过1了直接no

其他对于第i个人最多可以在赢 s[1]-s[i]场

我们可以把它看成网络流

a和b比赛之后流出去的流量为1

要么流向a要么流向b

每个人流出去的流量最多为s[i]-s[1]

然后源点向比赛连边,比赛向人连边,人向汇点连边

这样如果最大流是边满流,也就是源点流出去的水全到汇点了即为成立

否则为no

就比如这个样例吧

1的得分是2          2 3 4的得分都是1,他们都还能再赢一场,建出来的图就长这样

 

 最大流明显等于流出去的流量,答案为yes

代码如下

#include <bits/stdc++.h>
#include <iomanip>
#define fer(i,a,b) for(int i=a;i<=b;i++)
#define der(i,a,b) for(int i=a;i>=b;i--)
#define int long long
#define pb push_back
#define pll pair<int,int>
#define ld double
#define x first
#define y second
using namespace std;
const int N = 1e5+10, M = N*2, INF = 1e8;
int S, T;
int h[N], e[M], f[M], ne[M], idx;
int d[N], cur[N];
void add(int a, int b, int c)
{
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs()
{
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0, cur[S] = h[S];
    while (q.size())
    {
        auto t=q.front();
        q.pop();
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T)  return true;
                q.push(ver);
            }
        }
    }
    return false;
}
int find(int u, int limit)
{
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i])
    {
        cur[u] = i;  // 当前弧优化
        int ver = e[i];
        if (d[ver] == d[u] + 1 && f[i])
        {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}
int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}
int s[N],a[N];
int n,m1,m2;
int m;
signed main()
{
    int cas;
    cin>>cas;
    while (cas--)
    {

        int m1,m2;
        cin>>n>>m1>>m2;
        S=n+m2+1;//源点编号
        T=n+m2+2;//汇点编号
        idx=0;
        for (int i = 1; i <= n; i++)
            s[i] = 0;
        fer(i,0,n+m2+2) h[i] = -1 ;
        for (int i = 1; i <= m1; i++)
        {
            int x, y, z;
            cin>>x>>y>>z;
            if (z == 1)
                s[x]++;
            else
                s[y]++;
        }
        m=0;
        fer(i,1,m2)
        {
            int x , y ;
            cin >> x >> y ;
            if(x == 1 || y == 1)
            {
                s[1] ++ ;
            }
            else
            {
                m++;//流出去的流量
                add(n+i,x,1);//比赛向两个人连边
                add(n+i,y,1);
                add(S,n+i,1);//源点向比赛连边
            }
        }
        bool fl=false;
        fer(i,2,n){
            if(s[i]>s[1]){
                fl=true;
                break;
            }
            add(i,T,s[1]-s[i]);//每个人向汇点连边
        }
        if(fl){
            cout<<"NO\n";
            continue;
        }
        if(dinic()==m) cout << "YES" << '\n' ;
        else cout << "NO" << '\n' ;
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值