zoukankan      html  css  js  c++  java
  • 一道改编题目...

    Description

    某国要进行一场可怕的游戏,据说失败者会被秘密处决…
    402班的dalao jyq也被迫参加了这场比赛,由于乔神是 生命科学学科带头人 ,为了保存人类的科研成果,ljt必须救下他!

    一共有n个参赛者(编号从1开始,乔神编号为1)参加这场比赛,比赛两两对决,没有平局,必有胜者,胜者得一分,负者不得分。现在比赛已经进行了一半,每个选手已经有了一个积分gi0。给定接下来m场比赛的对战者(xi,yi)。现在ljt可以hack进比赛的操作系统,操纵一场比赛的胜负,但这样风险很大。因此ljt想要知道,在 保证乔神积分最高(不并列) 的前提下,最少的操纵次数是多少?

    Input

    • 第一行两个正整数n,m
    • 第二行n个数g1,g2,...,gn
    • 接下来m行,每行两个数,第i行为(xi,yi)

    Output

    • 如果无论如何操作都不能保证乔神夺冠,输出-1
    • 否则输出一个数,为最小操纵次数

    Sample

    Input1

    2 2
    1 2
    1 2
    1 2

    Output1

    2

    Input2

    8 14
    0 4 4 0 3 3 1 1 
    4 8
    8 6
    2 7
    3 1
    2 1
    3 4
    3 4
    4 1
    8 1
    7 6
    8 4
    2 1
    4 5
    4 2

    Output2

    12

    Hint

    • 对于10%的数据,n5,m10
    • 对于30%的数据,n10,m20
    • 对于60%的数据,n10,m40
    • 对于100%的数据,n12,m100

    Source

    yyh和ljt把一道题想复杂了,然后发现这个建模非常巧妙。
    然后就出出来祸害社会了。
    出题人水平有限,不保证数据绝对正确,如果有异议可以联系@ljt12138

    Solution

    首先是有上下界的最小费用可行流。不知道怎么看出来。

    枚举乔神胜场数量t=1,2,3,...,m,然后做如下建图:

    1. 对于一个与乔神无关的比赛matchi=(xi,yi),连接Smatchi,流量上下界都为2,费用为0,表示 一开始两人可能胜场数都+1matchixi,matchiyi,容量都为1,费用为0,表示最大可能胜场数+1;最关键的一条是:matchiT,容量为1,费用为1,表示放走一个流量,也即操纵比赛,至于谁赢则让网络流自己决定。
    2. 对于一个与乔神有关的比赛matchi=(1,yi),连接Smatchi,流量上下界都为1,费用为0,再连接matchiyi,表示一开始乔神是输的;连接yi1,容量为1费用为1,表示 让乔神赢。由于不可能让乔神输,这里不需要自动调整。
    3. 对于乔神1T,流量上下界都为t,即必须赢这么多场。
    4. 对于不是乔神的iT,容量为g1+tgi1,表示不能比乔神多或相等。
    5. 连接TS,容量为,变有源汇为无源汇。

    如何处理最小费用可行流?考虑流量下界的意义,即“必须流过”,得到的必须送出去,送出去的必须得到。不妨建立超级源汇SS,ST,对于一个必须流过wij的边ij,连接:
    1. SSj,容量为wij
    2. iST,容量为wij

    对新图求最小费用最大流。如果最大流没能将所有辅助边流满,说明不可行;否则最小费用为mcf得到的最小费用。

    最终答案就是所有最小费用的最小值。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 20, MAXM = 200;
    struct p {
        int x, y;
    } match[MAXM];
    int got[MAXN], n, m;
    
    struct node {
        int to, next, f, c, neg;
    } edge[10*MAXM];
    int head[MAXM*10], top = 0, sum_of_add = 0; // 辅助边流量和
    int S = 170, T = 171, SS = 172, ST = 173;
    void init()
    {
        memset(head, 0, sizeof head);
        top = sum_of_add = 0;
    }
    void push(int i, int j, int f, int c)
    {
        ++top, edge[top] = (node){j, head[i], f, c, top+1}, head[i] = top;
        ++top, edge[top] = (node){i, head[j], 0, -c, top-1}, head[j] = top;
    }
    void push_lim(int i, int j, int f) // 必须流过一定流量
    { push(SS, j, f, 0), push(i, ST, f, 0), sum_of_add += f; }
    
    int dis[MAXM], vis[MAXM], pre[MAXM], pre_edge[MAXM];
    queue<int> que;
    bool spfa(int &cost, int &flow)
    {
        memset(dis, 127/3, sizeof dis), memset(vis, 0, sizeof vis);
        memset(pre, 0, sizeof pre), memset(pre_edge, 0, sizeof pre_edge);
        for (dis[SS] = 0, que.push(SS), vis[SS] = 1; !que.empty(); que.pop()){
            int t = que.front(); vis[t] = 0;
            for (int i = head[t]; i; i = edge[i].next) {
                if (edge[i].f == 0 || dis[edge[i].to] <= dis[t]+edge[i].c) continue;
                int to = edge[i].to;
                dis[to] = dis[t] + edge[i].c;
                pre[to] = t, pre_edge[to] = i;
                if (!vis[to]) vis[to] = 1, que.push(to);
            }
        }
        if (dis[ST] > 233333333) return 0;
        int mn = INT_MAX;
        for (int i = ST; i != SS; i = pre[i]) mn = min(mn, edge[pre_edge[i]].f);
        for (int i = ST; i != SS; i = pre[i]) edge[pre_edge[i]].f -= mn, edge[edge[pre_edge[i]].neg].f += mn;
        flow += mn, cost += mn*dis[ST];
        return 1;
    }
    
    void mcf(int &cost, int &flow)
    {
        push(T, S, 233333333, 0);
        cost = flow = 0;
        while (spfa(cost, flow));
    }
    
    int ans_with_win(int t) // 1选手赢t场,需要的最少操作次数。
    {
        init();
        for (int i = 1; i <= m; i++) {
            if (match[i].x == 1) {
                push_lim(S, i, 1);
                push(i, m+match[i].y, 1, 0), push(m+match[i].y, m+1, 1, 1); // 让他赢
            } else {
                push_lim(S, i, 2);
                push(i, m+match[i].x, 1, 0), push(i, m+match[i].y, 1, 0);
                push(i, T, 1, 1); // 钦点,至于选谁无可奉告
            }
        }
        push_lim(m+1, T, t); // 赢t场
        for (int i = 2; i <= n; i++) {
            if (got[1]+t-got[i]-1 < 0) return INT_MAX;
            push(m+i, T, got[1]+t-got[i]-1, 0);
        }
        push(T, S, INT_MAX, 0); // 有源汇变无源汇
        int max_flow, min_cost;
        mcf(min_cost, max_flow);
        if (max_flow != sum_of_add) return INT_MAX;
        else return min_cost;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
            scanf("%d", &got[i]);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &match[i].x, &match[i].y);
            if (match[i].x > match[i].y) swap(match[i].x, match[i].y); // 交换
        }
        int ans = INT_MAX;
        for (int i = 0; i <= m; i++)
            ans = min(ans, ans_with_win(i));
        if (ans <= 233333333)
            cout << ans << endl;
        else
            cout << -1 << endl;
        return 0;
    }

    Other Thing

    造数据是坠痛苦的,因为对拍极其麻烦…

    顺便附上对拍用暴力吧..直接暴力枚举操作子集

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 20, MAXM = 105;
    struct p {
        int x, y;
    } match[MAXM];
    int got[MAXN], n, m;
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
            scanf("%d", &got[i]);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &match[i].x, &match[i].y);
            // if (match[i].x > match[i].y) swap(match[i].x, match[i].y); // 交换
        }
        int win[MAXN], ans = INT_MAX;
        for (int S = 1; S < 1<<m; S++) {
            int sub = S;
            do {
                memset(win, 0, sizeof win);
                int k = 0;
                for (int i = 1; i <= m; i++) {
                    if (((1<<(i-1))&S) == 0) {win[match[i].x]+=match[i].x != 1; win[match[i].y]+=match[i].y != 1;}
                    else if (((1<<(i-1))&sub) == 0) win[match[i].x]++, k++;
                    else win[match[i].y]++, k++;
                }
                int flag = 1;
                for (int i = 2; i <= n; i++) {
                    // if (S == 24575 && sub == 18521)
                        // cout << win[i] << " " << got[i] << endl;
                    if (win[i]+got[i] >= win[1]+got[1]) {flag = 0; break; }
                }
                if (flag) ans = min(ans, k);
                // if (k == 14 && flag) cout << S << " " << sub <<endl;
                if (sub == 0) break;
                sub = (sub-1)&S;
            } while (sub >= 0);
        }
        cout << ans << endl;
        return 0;
    }
    
  • 相关阅读:
    Sendkeys 和 Sendmessage 使用技巧一例
    和菜鸟一起学算法之二分法求极值问题
    和菜鸟一起学算法之三分法求极值问题
    和菜鸟一起学证券投资之国内生产总值GDP
    和菜鸟一起学OK6410之Led字符驱动
    和菜鸟一起学OK6410之最简单驱动模块hello world
    和菜鸟一起学OK6410之交叉编译hello world
    和菜鸟一起学android4.0.3源码之touchscreen配置+调试记录
    和菜鸟一起学android4.0.3源码之红外遥控器适配
    和菜鸟一起学OK6410之最简单字符驱动
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684324.html
Copyright © 2011-2022 走看看