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\)。还没学过,以后再来写。

posted @ 2025-05-08 19:12  maburb  阅读(39)  评论(0)    收藏  举报