一道改编题目...
Description
某国要进行一场可怕的游戏,据说失败者会被秘密处决…
402班的dalao jyq也被迫参加了这场比赛,由于乔神是 生命科学学科带头人 ,为了保存人类的科研成果,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%的数据,
n≤5,m≤10 - 对于30%的数据,
n≤10,m≤20 - 对于60%的数据,
n≤10,m≤40 - 对于100%的数据,
n≤12,m≤100
Source
yyh和ljt把一道题想复杂了,然后发现这个建模非常巧妙。
然后就出出来祸害社会了。
出题人水平有限,不保证数据绝对正确,如果有异议可以联系@ljt12138
Solution
首先是有上下界的最小费用可行流。不知道怎么看出来。
枚举乔神胜场数量
- 对于一个与乔神无关的比赛
matchi=(xi,yi) ,连接S→matchi ,流量上下界都为2 ,费用为0,表示 一开始两人可能胜场数都+1;matchi→xi,matchi→yi ,容量都为1,费用为0,表示最大可能胜场数+1;最关键的一条是:matchi→T ,容量为1,费用为1,表示放走一个流量,也即操纵比赛,至于谁赢则让网络流自己决定。 - 对于一个与乔神有关的比赛
matchi=(1,yi) ,连接S→matchi ,流量上下界都为1 ,费用为0,再连接matchi→yi ,表示一开始乔神是输的;连接yi→1 ,容量为1费用为1,表示 让乔神赢。由于不可能让乔神输,这里不需要自动调整。 - 对于乔神
1→T ,流量上下界都为t ,即必须赢这么多场。 - 对于不是乔神的
i→T ,容量为g1+t−gi−1 ,表示不能比乔神多或相等。 - 连接
T→S ,容量为∞ ,变有源汇为无源汇。
如何处理最小费用可行流?考虑流量下界的意义,即“必须流过”,得到的必须送出去,送出去的必须得到。不妨建立超级源汇
1.
2.
对新图求最小费用最大流。如果最大流没能将所有辅助边流满,说明不可行;否则最小费用为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;
}