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


A. Shuffle Hashing

题意:判断\(s\)排序后是不是和\(t\)的一段子串排序相等。

点击查看代码
void solve() {
    std::string s, t;
    std::cin >> s >> t;
    int n = s.size(), m = t.size();
    std::ranges::sort(s);
    for (int i = 0; i + n - 1 < m; ++ i) {
    	std::string ss = t.substr(i, n);
    	std::ranges::sort(ss);
    	if (ss == s) {
    		std::cout << "YES\n";
    		return;
    	}
    }
    std::cout << "NO\n";
}

B. A and B

题意:两个数\(a, b\)。第\(i\)次操作使得其中一个数加\(i\)。求几次操作使得\(a, b\)相等。

猜测是\(\frac{n(n+1)}{2}+|a-b|\)是偶数且\(\frac{n(n+1)}{2}\geq |a-b|\)时成立。

点击查看代码
void solve() {
    int a, b;
    std::cin >> a >> b;
    int c = std::abs(a - b);
    int ans = 0;
    i64 sum = 0;
    while (sum < c || (sum - c) % 2) {
    	++ ans;
    	sum += ans;
    }
    std::cout << ans << "\n";
}

C. Berry Jam

题意:\(2n\)个点,有些是\(1\),有些是\(2\)你需要从\(n\)\(n+1\)开始,每次删去相邻的一个点。求使得\(1, 2\)个数相同的最少操作数。

\(1\)是加一,\(2\)是减一。预处理一个向前得到\(d\)的贡献的最小距离。然后向后枚举值\(d\),与之前的\(-d\)的贡献相加去最小值。

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

    int cnt1 = std::ranges::count(a, 1);
    int cnt2 = std::ranges::count(a, 2);
    std::map<int, int> pre;
    int d = 0;
    pre[0] = 0;
    for (int i = n - 1; i >= 0; -- i) {
    	if (a[i] == 1) {
    		-- d;
    	} else {
    		++ d;
    	}
    	if (!pre.count(d)) {
    		pre[d] = n - i;
    	}
    }

    d = cnt1 - cnt2;
    int ans = pre.count(-d) ? pre[-d] : 2 * n;
    for (int i = n; i < 2 * n; ++ i) {
    	if (a[i] == 1) {
    		-- d;
    	} else {
    		++ d;
    	}

    	if (pre.count(-d)) {
    		ans = std::min(ans, i - n + 1 + pre[-d]);
    	}
    }
    std::cout << ans << "\n";
}

D. Segment Tree

题意:给你\(n\)个线段,如果两个线段相交但不包含就会连边。求能不能构成一棵树。

先按左端点排序,那么对于\(i\)来说前面和它相交的线段只需要满足右端点在\([l_i, r_i]\)之间。那么用树状数组维护区间和就可以得到边数。
如果边数不为\(n-1\)则不是。如果等于\(n-1\),再把所有边取出来看是不是一个联通块。因为已经确定只有\(n-1\)条边,可以用\(set\)暴力找。

点击查看代码
template <class T>
struct Fenwick {
    int n;
    std::vector<T> tr;

    Fenwick(int _n) {
        init(_n);
    }

    void init(int _n) {
        n = _n;
        tr.assign(_n + 1, T{});
    }

    void add(int x, const T &v) {
        for (int i = x; i <= n; i += i & -i) {
            tr[i] = tr[i] + v;
        }
    }

    T query(int x) {
        T res{};
        for (int i = x; i; i -= i & -i) {
            res = res + tr[i];
        }
        return res;
    }

    T sum(int l, int r) {
        return query(r) - query(l - 1);
    }
};

struct DSU {
	std::vector<int> fa, cnt;
	DSU(int _n) {
		init(_n);
	}

	void init(int _n) {
		fa.assign(_n, 0);
		cnt.assign(_n, 1);
		std::iota(fa.begin(), fa.end(), 0);
	}

	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	bool merge(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) {
			return false;
		}

		fa[y] = x;
		cnt[x] += cnt[y];
		return true;
	}

	bool same(int x, int y) {
		return find(x) == find(y);
	}

	int size(int x) {
		return cnt[find(x)];
	}
};

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

    i64 cnt = 0;
    std::ranges::sort(a);
    Fenwick<int> tr(2 * n);
    for (auto & [l, r, id] : a) {
		int v = tr.sum(l, r);
		cnt += v;
    	tr.add(r, 1);
    }

    if (cnt == n - 1) {
    	DSU dsu(n);
    	std::set<std::pair<int, int>> s;
    	for (auto & [l, r, id] : a) {
    		auto it = s.lower_bound({l, 0}), ij = s.upper_bound({r, 0});
    		while (it != ij) {
    			dsu.merge(id, it->second);
    			++ it;
    		}
    		s.emplace(r, id);
    	}

    	for (int i = 1; i < n; ++ i) {
    		if (!dsu.same(0, i)) {
    			std::cout << "NO\n";
    			return;
    		}
    	}
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

E. Tests for problem D

题意:和\(D\)相反。给你树,要求构造一个满足条件的\(n\)个区间。端点在\([1, 2n]\)之间,且互不相同。

\(n\)个点正好用完\([1, 2n]\)之间的点,所以猜测对于子树大小为\(size_i\)的点,它的区间应该为\([l_i, l_i + size_i + 1]\),且它的子节点的左端点都在这个区间里。为了人子节点的区间不相交,相离比较困难,可以使得它们互相包含,也就是人左端点小的子节点右端点尽可能大。那么我们可以\(dfs\)遍历,每次确定左端点和右端点,然后从左端点大的子节点遍历,得到它的右端点,那么下一个子节点的右端点要大于这个右端点,同理为了节省空间,应该是紧挨着的。

点击查看代码
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<int> l(n), r(n);
    int idx = 0;
    auto dfs = [&](auto & self, int u, int R, int fa) -> int {
    	r[u] = R + adj[u].size();
    	int idx = r[u] - 1;
    	R = r[u];
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		l[v] = idx -- ;
    		R = self(self, v, R, u);
    	}
    	return R;
    };

    l[0] = 1;
    dfs(dfs, 0, 2, -1);
    for (int i = 0; i < n; ++ i) {
    	std::cout << l[i] << " " << r[i] << "\n";
    }
}
posted @ 2025-05-06 21:09  maburb  阅读(19)  评论(0)    收藏  举报