2018ACM-ICPC中国大学生程序设计竞赛线上赛
准备按照https://www.cnblogs.com/WIDA/p/18122995这个网页整理的往年\(xcpc\)真题刷题和\(vp\)。
B. Goldbach
题意:\(n\)中产品,生产第\(i\)种产品可以获得\(a_i\)的收益,\(m\)种矿物,采集第\(i\)种矿物需要支出\(b_i\)。每组产品需要生成一些其它产品和挖掘一些矿物才能生产,一个产品只要能生产就能有收益。求最大利润,也就是使得收益-支出最大。
如果对于每个产品,他需要\(x\)个产品和\(y\)个矿物才能生产,如果我们把产品和矿物都看作一个点,让它对这些产品和矿物连边,问题就变成每个点有权值,你需要找一个子图,使得这个子图没有边连向子图外的点,求最大利润。
这就是最大权闭合图的问题。可以用网络流解决。
闭合子图: 给定一个有向图 G(V,E),图中的某一个点集,这个点集满足内部的所有边不能从点集里面指向点集外面,则将这个点集和点集里面的边统称为原图的闭合子图
最大权闭合子图: 给定一个有向图 G(V,E),每个点上有一个权值,图中存在若干个闭合子图,若其中一个闭合子图的点集权值和最大,则这个闭合子图称为最大权闭合子图
算法:原图边容量为正无穷,虚拟源点向正权点连边,负权点向虚拟汇点连边,容量为点权绝对值
最大权闭合图 = 所有正权点之和 – 最小割。
点击查看代码
const int N = 410, M = (N * N + N) * 2 + 10;
i64 INF = 1e18;
int head[N], ver[M], next[M], tot;
i64 cap[M];
int d[N], cur[N];
int n, m, S, T;
void add(int x, int y, i64 z) {
ver[tot] = y; cap[tot] = z; next[tot] = head[x]; head[x] = tot ++ ;
ver[tot] = x; cap[tot] = 0; next[tot] = head[y]; head[y] = tot ++ ;
}
bool bfs() {
memset(d, -1, sizeof d);
std::queue<int> q;
d[S] = 0, cur[S] = head[S];
q.push(S);
while (q.size()) {
int u = q.front(); q.pop();
for (int i = head[u]; ~i ; i = next[i]) {
int v = ver[i];
if (d[v] == -1 && cap[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == T) {
return true;
}
q.push(v);
}
}
}
return false;
}
i64 find(int u, i64 limit) {
if (u == T) {
return limit;
}
i64 flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = next[i]) {
int v = ver[i];
cur[u] = i;
if (d[v] == d[u] + 1 && cap[i]) {
int t = find(v, std::min(cap[i], limit - flow));
if (!t) {
d[v] = -1;
} else {
cap[i] -= t; cap[i ^ 1] += t;
flow += t;
}
}
}
return flow;
}
i64 dinic() {
i64 r = 0, flow;
while (bfs()) {
while (flow = find(S, INF)) {
r += flow;
}
}
return r;
}
void solve() {
int n, m;
std::cin >> n >> m;
memset(head, -1, sizeof head);
std::vector<i64> a(n), b(m);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < m; ++ i) {
std::cin >> b[i];
}
S = n + m + 1, T = n + m + 2;
for (int i = 1; i <= n; ++ i) {
add(S, i, a[i - 1]);
}
for (int i = 1; i <= m; ++ i) {
add(i + n, T, b[i - 1]);
}
for (int i = 1; i <= n; ++ i) {
int n1, n2;
std::cin >> n1 >> n2;
while (n1 -- ) {
int j;
std::cin >> j;
add(i, j + n, INF);
}
while (n2 -- ) {
int j;
std::cin >> j;
add(i, j, INF);
}
}
i64 ans = std::accumulate(a.begin(), a.end(), 0ll) - dinic();
std::cout << ans << "\n";
}
G. Trouble of Tyant
题意:\(n\)个点\(2n - 3\)条边,\([2, n]\)的点都和\(1\)有边,\(i\in [3, n]\)的点都和\(i - 1\)有边。多次询问,如果每条边都加上\(d\)。\(1\)到\(n\)的最短路是多少。

图大概就是长这样。每次跑\(dijkstra\)显然不行。观察性质,发现最短路一定是从\(1\)到\(i\),然后到\(i+1, i+2, ..., n-1\)再到\(n\),一共\(n - i + 1\)条边。
记\(a_i\)为\((1, i)\)的边,那么我们预处理\(suf_i\)表示\(i\)一直往后走到\(n\)的距离,那么对于\(i\),它到\(n\)的每条边都加上\(d\)后距离就是\(a_i + suf_i + (n - i + 1)\times d\)。我们要求这个最小,发现这其实是一个一次函数的表示:\(y = kx + b\)。那么我们可以用李超线段树实现。
做这道题也是想了挺久,因为李超线段树之前学过一次还没用过。
点击查看代码
const int N = 1e6 + 5;
const i64 INF = 1e18;
#define ls (u << 1)
#define rs (u << 1 | 1)
struct Line {
i64 k, b;
bool on;
i64 val(i64 x) {
return k * x + b;
}
};
struct LiChao {
std::vector<Line> tr;
int sign;
LiChao(int n, int _sign) : tr(n << 2), sign(_sign) {}
void modify(int u, int l, int r, Line line) {
if (!tr[u].on) {
tr[u] = line;
return;
}
int mid = l + r >> 1;
if (sign * tr[u].val(mid) < sign * line.val(mid)) {
std::swap(tr[u], line);
}
if (sign * tr[u].val(l) < sign * line.val(l)) {
modify(ls, l, mid, line);
}
if (sign * tr[u].val(r) < sign * line.val(r)) {
modify(rs, mid + 1, r, line);
}
}
i64 query(int u, int l, int r, int x) {
if (!tr[u].on) {
return sign == 1 ? -INF : INF;
}
if (l == r) {
return tr[u].val(x);
}
int mid = l + r >> 1;
if (x <= mid) {
if (sign == 1) {
return std::max(tr[u].val(x), query(ls, l, mid, x));
} else {
return std::min(tr[u].val(x), query(ls, l, mid, x));
}
} else {
if (sign == 1) {
return std::max(tr[u].val(x), query(rs, mid + 1, r, x));
} else {
return std::min(tr[u].val(x), query(rs, mid + 1, r, x));
}
}
}
};
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<int> a(n);
for (int i = 1; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> b(n);
for (int i = 2; i < n; ++ i) {
std::cin >> b[i];
}
std::vector<i64> suf(n);
for (int i = n - 2; i >= 1; -- i) {
suf[i] = suf[i + 1] + b[i + 1];
}
int N = 1e5;
LiChao tr(N, -1);
for (int i = 1; i + 1 < n; ++ i) {
Line line = {n - i, a[i] + suf[i], true};
tr.modify(1, 1, N, line);
}
tr.modify(1, 1, N, Line{1, a[n - 1], true});
while (q -- ) {
int d;
std::cin >> d;
std::cout << tr.query(1, 1, N, d) << " \n"[!q];
}
}
G Rock Paper Sccissors Lizard Spock
貌似是\(fft\)。还没学过,以后再来写。

浙公网安备 33010602011771号