VP Educational Codeforces Round 67 (Rated for Div. 2)


A. Stickers and Toys

题意:\(n\)个罐子,有\(s\)个贴纸和\(t\)个玩具在里面,一个罐子最多有一个贴纸和一个玩具。求至少开几个罐子才能或者至少一个贴纸和一个玩具。

只需要开\(n - s + 1\)个罐子就能有贴纸,开\(n - t + 1\)个就能有玩具。所以答案是这两个的最大值。

点击查看代码
void solve() {
    int n, s, t;
    std::cin >> n >> s >> t;
    std::cout << n - std::min(s, t) + 1 << "\n";
}

B. Letters Shop

题意:给你一个字符串\(s\),询问若干个字符串\(t\),找\(s\)的一个最短的前缀,使得对于\(t\)出现的每个字母这个前缀的这个字母的出现次数都大于等于\(t\)的这个字母的出现次数。

前缀和预处理字母个数,然后每个询问二分。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    std::vector cnt(n + 1, std::array<int, 26>{});
    for (int i = 0; i < n; ++ i) {
    	cnt[i + 1] = cnt[i];
    	cnt[i + 1][s[i] - 'a'] += 1;
    }

    int m;
    std::cin >> m;
    while (m -- ) {
    	std::string t;
    	std::cin >> t;
	    std::array<int, 26> cnt1{};
	    for (auto & c : t) {
	    	++ cnt1[c - 'a'];
	    }

    	auto check = [&](int r) -> bool {
    		for (int i = 0; i < 26; ++ i) {
    			if (cnt[r][i] < cnt1[i]) {
    				return false;
    			}
    		}

    		return true;
    	};

    	int l = 1, r = n;
    	while (l < r) {
    		int mid = l + r >> 1;
    		if (check(mid)) {
    			r = mid;
    		} else {
    			l = mid + 1;
    		}
    	}

    	std::cout << l << "\n";
    }
}

C. Vasya And Array

题意:一个数组,\(m\)个条件,一种是\([l, r]\)是有序的,一种是\([l, r]\)是乱序的。构造一个符合条件的数组。

把有序的\([l, r]\)都拿出来,记\(R[i]\)表示经过一些区间可以覆盖\([i, r]\)的最远的\(r\)。这个可以双层循环求。
然后判断每个乱序的区间是不是被有序区间包含,如果是就无解。
否则就把有序的区间从后往前对于每个区间里的数从小到大放,这样就能保证每个区间有序,且每个区间的对于后面的区间都是无序。这样肯定能满足无序区间的条件。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> R(n + 1);
    std::ranges::iota(R, 0);
    std::vector<std::pair<int, int>> a;
    for (int i = 0; i < m; ++ i) {
    	int t, l, r;
    	std::cin >> t >> l >> r;
    	if (t == 1) {
    		R[l] = std::max(R[l], r);
    	} else {
    		a.emplace_back(l, r);
    	}
    }

    for (int i = 1, pre = 1; i <= n; ++ i) {
    	int r = std::max(pre, R[i]);
    	for (int j = i; j <= r; ++ j) {
    		r = std::max(r, R[j]);
    	}

    	R[i] = r;
    	pre = std::max(pre, R[i]);
    }

    for (auto & [l, r] : a) {
    	if (R[l] >= r) {
    		std::cout << "NO\n";
    		return;
    	}
    }

    std::vector<std::pair<int, int>> b;
    for (int i = 1; i <= n; ++ i) {
    	b.emplace_back(i, R[i]);
    	i = R[i];
    }

    std::reverse(b.begin(), b.end());
    std::vector<int> ans(n + 1);
    int x = 1;
    for (auto & [l, r] : b) {
    	for (int i = l; i <= r; ++ i) {
    		ans[i] = x ++ ;
    	}
    }

    std::cout << "YES\n";
    for (int i = 1; i <= n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n];
    }
}

D. Subarray Sorting

题意:给你两个数组\(a, b\),你每次可以给\(a\)的一个子数组排序,求能不能使得\(a\)变成\(b\)

一段区间排序,可以拆分为两个两个交换,这类似于冒泡排序。所以我们可以每次只给一个长度为\(2\)的区间排序。
那么我们从前往后看,如果\(a\)中和\(b_i\)相等的最近的数想要到\(i\)给位置,那么需要满足它是前面数最小的。于是我们用线段树维护区间最小值,每次找到\(b_i\)就判断它是不是前缀里最小的数,然后把它更改为无穷大,这样相当于把它删去。

点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)

template <class Info>
struct Node {
	int l, r;
	Info info;
};

template <class Info>
struct SegmentTree {
	std::vector<Node<Info> > tr;
	SegmentTree(int _n) {
		init(_n);
	}

	SegmentTree(std::vector<Info> & a) {
		init(a);
	}

	void init(int _n) {
		tr.assign(_n << 2, {});
		build(0, _n - 1);
	}

	void init(std::vector<Info> & a) {
		int _n = (int)a.size();
		tr.assign(_n << 2, {});
		build(0, _n - 1, a);
	}

	void pushup(int u) {
		tr[u].info = tr[ls].info + tr[rs].info;
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, ls); build(mid + 1, r, rs);
	}

	void build(int l, int r, std::vector<Info> & a, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			tr[u].info = a[l];
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, a, ls); build(mid + 1, r, a, rs);
		pushup(u);
	}

	void modify(int p, Info add, bool set = false) {
		int u = 1;
		while (tr[u].l != tr[u].r) {
			int mid = umid;
			if (p <= mid) {
				u = ls;
			} else {
				u = rs;
			}
		}

		if (set) {
			tr[u].info = add;
		} else {
			tr[u].info = tr[u].info + add;
		}

		u >>= 1;
		while (u) {
			pushup(u);
			u >>= 1;
		}
	}

	Info query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].info;
		}

		int mid = umid;
		if (r <= mid) {
			return query(l, r, ls);
		}  else if (l > mid) {
			return query(l, r, rs);
		}

		return query(l, r, ls) + query(l, r, rs);
	}
};

struct Info {
	int min;
};

Info operator + (const Info & a, const Info & b) {
	Info res{};
	res.min = std::min(a.min, b.min);
	return res;
}

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    }

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    	-- b[i];
    }

    std::vector<std::queue<int>> q(n);
    std::vector<Info> info(n);
    for (int i = 0; i < n; ++ i) {
    	q[a[i]].push(i);
    	info[i] = {a[i]};
    }

    const int inf = 1e9;
    SegmentTree<Info> tr(info);
    for (int i = 0; i < n; ++ i) {
    	if (q[b[i]].empty()) {
    		std::cout << "NO\n";
    		return;
    	}

    	int p = q[b[i]].front(); q[b[i]].pop();
    	if (tr.query(0, p).min != a[p]) {
    		std::cout << "NO\n";
    		return;
    	}

    	tr.modify(p, Info{inf}, true);
    }

    std::cout << "YES\n";
}	

E. Tree Painting

题意:一个树,选一个根使得所有点的子树和的和最大。

考虑换根\(dp\)。先选一号点为根,得到\(f_u\)表示\(u\)这棵子树的答案。\(f_u = size_u + \sum_v f_v\)
然后换根,对于\(u, v\),把\(v\)换成根,那么要减去\(v\)\(u\)的贡献得到\(u\)除去\(v\)这棵子树的贡献,然后把\(u\)的贡献加到\(v\)就行。
\(f_v = f_u - f_v - size_v + n - size_v\)

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::vector<int>> adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector<i64> f(n), size(n);
    auto dfs = [&](auto & self, int u, int fa) -> void {
    	size[u] = 1;
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		self(self, v, u);
    		size[u] += size[v];
    		f[u] += f[v];
    	}

    	f[u] += size[u];
    };

    dfs(dfs, 0, -1);
    i64 ans = f[0];

    auto dfs1 = [&](auto & self, int u, int fa) -> void {
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		f[v] = f[u] - f[v] - size[v] + f[v] + n - size[v];
    		ans = std::max(ans, f[v]);
    		self(self, v, u);
    	}
    };

    dfs1(dfs1, 0, -1);
    std::cout << ans << "\n";
}

F. Expected Square Beauty

待补。


G. Gang Up

题意:一个图,有\(k\)个人要到\(1\)号点,一个人在\(t\)时刻到达\(1\)号点则代价为\(t\times c\),一条边在同一时刻有\(a\)个人走则代价为\(d\times a^2\)。求最小代价。

考虑费用流。
那么对于时刻,可以建分层图,对于\(d\times a^2\),可以得到\(a^2 - (a -1)^ 2 = 2\times a - 1\),于是可以建\(k\)条边表示。
其它建边就是每一时刻给原图的边都建一下,然后因为可以停留,所以\(t\)时刻\(i\)点可以无费用到\(i+1\)时刻。
然后是时刻的最大值,每个人最多走\(n-1\)条边,可能等其它\(k-1\)个人都走完再走,直接大一点设为\(n\times k\)

点击查看代码
template<class T>
struct MinCostFlow {
    struct _Edge {
        int to;
        T cap;
        T cost;
        _Edge(int to_, T cap_, T cost_) : to(to_), cap(cap_), cost(cost_) {}
    };
    int n;
    std::vector<_Edge> e;
    std::vector<std::vector<int>> g;
    std::vector<T> h, dis;
    std::vector<int> pre;
    bool dijkstra(int s, int t) {
        dis.assign(n, std::numeric_limits<T>::max());
        pre.assign(n, -1);
        std::priority_queue<std::pair<T, int>, std::vector<std::pair<T, int>>, std::greater<std::pair<T, int>>> que;
        dis[s] = 0;
        que.emplace(0, s);
        while (!que.empty()) {
            T d = que.top().first;
            int u = que.top().second;
            que.pop();
            if (dis[u] != d) {
                continue;
            }
            for (int i : g[u]) {
                int v = e[i].to;
                T cap = e[i].cap;
                T cost = e[i].cost;
                if (cap > 0 && dis[v] > d + h[u] - h[v] + cost) {
                    dis[v] = d + h[u] - h[v] + cost;
                    pre[v] = i;
                    que.emplace(dis[v], v);
                }
            }
        }
        return dis[t] != std::numeric_limits<T>::max();
    }
    MinCostFlow() {}
    MinCostFlow(int n_) {
        init(n_);
    }
    void init(int n_) {
        n = n_;
        e.clear();
        g.assign(n, {});
    }
    void addEdge(int u, int v, T cap, T cost) {
        g[u].push_back(e.size());
        e.emplace_back(v, cap, cost);
        g[v].push_back(e.size());
        e.emplace_back(u, 0, -cost);
    }
    std::pair<T, T> flow(int s, int t) {
        T flow = 0;
        T cost = 0;
        h.assign(n, 0);
        while (dijkstra(s, t)) {
            for (int i = 0; i < n; ++i) {
                h[i] += dis[i];
            }
            T aug = std::numeric_limits<int>::max();
            for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
                aug = std::min(aug, e[pre[i]].cap);
            }
            for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
                e[pre[i]].cap -= aug;
                e[pre[i] ^ 1].cap += aug;
            }
            flow += aug;
            cost += aug * h[t];
        }
        return std::make_pair(flow, cost);
    }
    struct Edge {
        int from;
        int to;
        T cap;
        T cost;
        T flow;
    };
    std::vector<Edge> edges() {
        std::vector<Edge> a;
        for (int i = 0; i < e.size(); i += 2) {
            Edge x;
            x.from = e[i + 1].to;
            x.to = e[i].to;
            x.cap = e[i].cap + e[i + 1].cap;
            x.cost = e[i].cost;
            x.flow = e[i + 1].cap;
            a.push_back(x);
        }
        return a;
    }
};

void solve() {
    int n, m, k, c, d;
    std::cin >> n >> m >> k >> c >> d;
    std::vector<int> a(k);
    for (int i = 0; i < k; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    }

    auto get = [&](int t, int i) -> int {
    	return t * n + i;
    };

    const int inf = 1e9;
    MinCostFlow<int> f((n + k + 1) * n + 2);
    int S = (n + k + 1) * n, T = (n + k + 1) * n + 1;
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	for (int t = 0; t < n + k; ++ t) {
    		for (int i = 1; i <= k; ++ i) {
    			f.addEdge(get(t, u), get(t + 1, v), 1, d * (2 * i - 1));
    			f.addEdge(get(t, v), get(t + 1, u), 1, d * (2 * i - 1));
    		}
    	}
    }

    for (int i = 0; i < n; ++ i) {
    	for (int t = 0; t < n + k; ++ t) {
    		f.addEdge(get(t, i), get(t + 1, i), inf, 0);
    	}
    }

    for (int t = 0; t <= n + k; ++ t) {
    	f.addEdge(get(t, 0), T, inf, t * c);
    }

    for (int i = 0; i < k; ++ i) {
    	f.addEdge(S, get(0, a[i]), 1, 0);
    }

    auto [flow, cost] = f.flow(S, T);
    std::cout << cost << "\n";
}
posted @ 2025-04-24 16:36  maburb  阅读(14)  评论(0)    收藏  举报