一道改编题目...

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;
}
posted @ 2017-04-06 23:38  ljt12138  阅读(354)  评论(0编辑  收藏  举报