VP Educational Codeforces Round 29


A. Quasi-palindrome

题意:给你一个数,在前面加上一些前导零使得这个数是回文。

模拟。

点击查看代码
void solve() {
	std::string s;
	std::cin >> s;
	auto check = [&](std::string s) -> bool {
		int l = 0, r = (int)s.size() - 1;
		while (l < r) {
			if (s[l] != s[r]) {
				return false;
			}

			++ l, -- r;
		}

		return true;
	};

	for (int i = 0; i <= 10; ++ i) {
		if (check(s)) {
			std::cout << "YES\n";
			return;
		}

		s = "0" + s;
	}

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

B. Kayaking

题意:有\(2\times n\)个人,\(n - 2\)个可以坐两个人的船和\(2\)个可以坐一个人的船,船的代价是两个人的重量的差的绝对值,求最小总代价。

枚举坐一个船的两个人,其它人排序后从小到大两两分组。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    n *= 2;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::sort(a.begin(), a.end());
    // std::cout << std::min({a[n - 2] - a[1], a[n - 3] - a[0], a[n - 1] - a[2]}) << "\n";
    // return

    int ans = 2e9;
    for (int i = 0; i < n; ++ i) {
    	for (int j = i + 1; j < n; ++ j) {
    		std::vector<int> b;
    		for (int k = 0; k < n; ++ k) {
    			if (k != i && k != j) {
    				b.push_back(a[k]);
    			}
    		}

    		int sum = 0;
    		for (int k = 0; k + 1 < b.size(); k += 2) {
    			sum += b[k + 1] - b[k];
    		}

    		ans = std::min(ans, sum);
    	}
    }

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

C. 1-2-3

题意:两个人石头剪刀布,\(2 > 1, 3 > 2, 1 > 3\)。第一个人一开始出\(a\),第二个一开始出\(b\),每个人下一轮的策略是固定的,第一个下一轮会出\(A[a][b]\),第二个下一轮会出\(B[a][b]\)。求\(n\)轮后每个人的得分。

观察到\(n\)非常大,但每一轮的策略是固定的,可以倍增求出\(f[i][j][k], g[i][j][k]\)表示如果当前两个人出的是\(i, j\),那么\(2^k\)轮后第一个人出的是\(f[i][j][k]\),第二个人出的是\(g[i][j][k]\),同理记录\(sa[i][j][k], sb[i][j][k]\)表示\(k\)轮后两个人的得分。那么将\(n\)二进制拆解后一步一步跳就可以得到答案。

点击查看代码
const int N = 4;

int A[N][N], B[N][N];
int f[N][N][61], g[N][N][61];
i64 sa[N][N][61], sb[N][N][61];

void solve() {
    i64 n;
    int a, b;
    std::cin >> n >> a >> b;
    for (int i = 1; i <= 3; ++ i) {
    	for (int j = 1; j <= 3; ++ j) {
    		std::cin >> A[i][j];
    	}
    }

    for (int i = 1; i <= 3; ++ i) {
    	for (int j = 1; j <= 3; ++ j) {
    		std::cin >> B[i][j];
    	}
    }

    auto get = [&](int a, int b) -> int {
    	if (a == b) {
    		return 0;
    	}

    	if (a == b + 1 || a + 2 == b) {
    		return 1;
    	}

    	return 2;
    };

    for (int i = 1; i <= 3; ++ i) {
    	for (int j = 1; j <= 3; ++ j) {
    		f[i][j][0] = A[i][j];
    		g[i][j][0] = B[i][j];
    		if (get(i, j) == 1) {
    			sa[i][j][0] = 1;
    		} else if (get(i, j) == 2) {
    			sb[i][j][0] = 1;
    		} 
    	}
    }

    for (int k = 1; k <= 60; ++ k) {
    	for (int i = 1; i <= 3; ++ i) {
    		for (int j = 1; j <= 3; ++ j) {
    			f[i][j][k] = f[f[i][j][k - 1]][g[i][j][k - 1]][k - 1];
    			g[i][j][k] = g[f[i][j][k - 1]][g[i][j][k - 1]][k - 1];
    			sa[i][j][k] = sa[i][j][k - 1] + sa[f[i][j][k - 1]][g[i][j][k - 1]][k - 1];
    			sb[i][j][k] = sb[i][j][k - 1] + sb[f[i][j][k - 1]][g[i][j][k - 1]][k - 1];
    		}
    	}
    }

    i64 ansa = 0, ansb = 0;
    for (i64 i = 60; i >= 0; -- i) {
    	if (n >> i & 1) {
    		ansa += sa[a][b][i];
    		ansb += sb[a][b][i];
    		int na = f[a][b][i];
    		int nb = g[a][b][i];
    		a = na, b = nb;
    	}
    }

    std::cout << ansa << " " << ansb << "\n";
}

D. Yet Another Array Queries Problem

题意:对一个长度为\(n\)的数组进行\(q\)次操作,每次将一个区间整体右移或者将一个区间翻转。然后询问\(m\)次,每次问第\(m_i\)个位置上的数是什么。

\(n, q\)都是\(1e5\),而\(m\)只有\(100\),那么我们应该从\(m\)考虑时间复杂度。
考虑反过来操作,做每个操作的逆操作,翻转操作的逆操作还是翻转,右移操作的逆操作是左移。如果先进行翻转然后右移,那么我们先进行左移然后翻转,模拟一下得到其结果是正确的,同理模拟先右移在翻转,翻转两次,右移两次的情况,发现进行逆操作都可以得到正确的位置。那么对于这些操作我们就都可以反过来操作。

点击查看代码
void solve() {
    int n, q, m;
    std::cin >> n >> q >> m;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::vector<std::array<int, 3>> Q(q);
    for (int i = 0; i < q; ++ i) {
    	int t, l, r;
    	std::cin >> t >> l >> r;
    	-- l, -- r;
    	Q[i] = {t, l, r};
    }

    std::reverse(Q.begin(), Q.end());

    while (m -- ) {
    	int x;
    	std::cin >> x;
    	-- x;
    	for (auto & [t, l, r] : Q) {
    		if (t == 1) {
    			if (x > l && x <= r) {
    				-- x;
    			} else if (x == l) {
    				x = r;
    			}
    		} else {
    			if (l <= x && x <= r) {
    				x = r - (x - l + 1) + 1;
    			}
    		}
    	}

    	std::cout << a[x] << " \n"[!m];
    }
}

E. Turn Off The TV

题意:给出\(n\)个线段,求有没有一个线段被其它线段的并集覆盖。

如果我们给每个线段覆盖的区间的每个位置都加一,那么一个线段是被其它线段覆盖的等价于这个线段的区间的每个位置的值都大于等于\(2\)
那么就变成了求区间最小值的问题。可以用线段树维护。
可以用动态开点线段树,也可以对点离散化后用线段树,不过离散化要注意把\(l, r\)左右两边的点也加进来,不然可能会有两个并不相邻的点离散化后相邻导致判断错误。

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

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

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

	LazySegmentTree(std::vector<Info> & a) {
		int _n = (int)a.size();
		init(_n, a);
	}

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

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

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

	void pushdown(Node<Info, Tag> & u, Tag tag) {
		u.info = u.info + tag;
		u.tag = u.tag + tag;
	}

	void pushdown(int u) {
		if (tr[u].tag.exist()) {
			pushdown(tr[ls], tr[u].tag);
			pushdown(tr[rs], tr[u].tag);
			tr[u].tag.clear();
		}
	}

	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);
		pushup(u);
	}

	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 l, int r, Tag tag, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			pushdown(tr[u], tag);
			return;
		}

		pushdown(u);
		int mid = umid;
		if (l <= mid) {
			modify(l, r, tag, ls);
		}

		if (r > mid) {
			modify(l, r, tag, rs);
		}

		pushup(u);
	}

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

		pushdown(u);

		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);
	}

	// int query_first_not_appear(int u = 1) {
	// 	if (tr[u].l == tr[u].r) {
	// 		return tr[u].l;
	// 	}

	// 	pushdown(u);
	// 	int mid = umid;
	// 	if (tr[ls].info.sum != tr[ls].info.len) {
	// 		return query_first_not_appear(ls);
	// 	} else {
	// 		return query_first_not_appear(rs);
	// 	}
	// }
};

struct Info {	
	int min;
};

struct Tag {
	int add;
	bool exist() {	
		return add != 0;
	}

	void clear() {
		add = 0;
	}
};

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

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

Tag operator + (const Tag & a, const Tag & b) {
	Tag res{};
	res.add = a.add + b.add;
	return res;
}

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

    std::sort(b.begin(), b.end());
    b.erase(std::unique(b.begin(), b.end()), b.end());

    auto get = [&](int x) -> int {
    	return std::lower_bound(b.begin(), b.end(), x) - b.begin();
    };

    for (auto & [l, r] : a) {
    	l = get(l);
    	r = get(r);
    }

    int m = b.size();
    LazySegmentTree<Info, Tag> tr(m);
    for (auto & [l, r] : a) {
    	tr.modify(l, r, Tag{1});
    }

    for (int i = 0; i < n; ++ i) {
    	auto & [l, r] = a[i];
    	if (tr.query(l, r).min >= 2) {
    		std::cout << i + 1 << "\n";
    		return;
    	}
    }

    std::cout << -1 << "\n";
}

F. Almost Permutation

题意:有\(n\)个数和\(m\)条限制,对于\([l_i, r_i]\)的每个数要大于等于\(v_i\)或者小于等于\(v_i\),且每个数的范围在\(1\)\(n\)之间。你要构造一个符合条件的数组,记\(cnt_i\)\(i\)在数组里出现的次数,代价则为\(\sum_{i=1}^{n} (cnt_i)^2\)。你要使得代价最小。

首先根据这些限制可以得到每个数的取值范围\(min_i, max_i\),如果\(min_i > max_i\)则无解。
那么我们可以用费用流求解这个问题,每个位置只有一个数,所以源点向每个位置连流量为\(1\)费用为\(0\)的边,同时\(i\)\([min_i, max_i]\)各连一条流量为\(1\)费用为\(0\)的边。最后关键的是,我们如何用边的费用表示\((cnt_i)^2\),一条边显然不行,但我们可以用\(n\)条边来表示,第一条边流量为\(1\)费用为\(1^2 - 0^2\),第二条边流量为\(1\),费用为\(2^2 - 1^2\),第三条边费用为\(3^2 - 2^2\)...,这样我们就用\(i^2 - (i-1)^2\)的差来表示了每多选一个增加的代价。

点击查看代码
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;
    std::cin >> n >> m;
    std::vector<std::array<int, 4>> a(m);
    for (int i = 0; i < m; ++ i) {
    	int t, l, r, v;
    	std::cin >> t >> l >> r >> v;
    	-- l, -- r;
    	a[i] = {t, l, r, v};
    }

    std::vector<int> min(n, 1), max(n, n);
    for (int i = 0; i < n; ++ i) {
    	for (auto & [t, l, r, v] : a) {
    		if (l <= i && i <= r) {
    			if (t == 1) {
    				min[i] = std::max(min[i], v);
    			} else {
    				max[i] = std::min(max[i], v);
    			}
    		}
    	}

    	if (min[i] > max[i]) {
    		std::cout << -1 << "\n";
    		return;
    	}
    }

    MinCostFlow<i64> f(2 * n + 2);
    int s = 2 * n, t = 2 * n + 1;
    for (int i = 0; i < n; ++ i) {
    	f.addEdge(s, i, 1, 0);
    	for (int j = min[i]; j <= max[i]; ++ j) {
    		f.addEdge(i, j - 1 + n, 1, 0);
    	}
    }

    for (int i = 0; i < n; ++ i) {
    	f.addEdge(i + n, t, 1, 1);
    	for (int j = 2; j <= n; ++ j) {
    		f.addEdge(i + n, t, 1, (i64)j * j - (i64)(j - 1) * (j - 1));
    	}
    }

    auto [flow, cost] = f.flow(s, t);
    std::cout << cost << "\n";
}
posted @ 2025-03-10 16:50  maburb  阅读(24)  评论(0)    收藏  举报