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


A. Chess For Three

记录观众是哪一个,观众不能是赢家。

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

    int x = 1, y = 2, z = 3;
    for (int i = 0; i < n; ++ i) {
    	if (a[i] == z) {
    		std::cout << "NO\n";
    		return;
    	}

    	if (a[i] == x) {
    		std::swap(y, z);
    	} else {
    		std::swap(x, z);
    	}
    }

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

B. Beautiful Divisors

枚举。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    for (int i = n; i >= 1; -- i) {
    	for (int j = 0; j < 10; ++ j) {
    		if ((((1 << j) - 1) << (j - 1)) == i && n % i == 0) {
    			std::cout << i << "\n";
    			return;
    		}
    	}
    }
}

C. Rumor

题意:求每个联通块的最小值的和。

dfs求联通块即可。

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

    std::vector<std::vector<int>> adj(n);
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector<int> st(n);
    int min = 2e9;
    auto dfs = [&](auto & self, int u) -> void {
    	min = std::min(min, a[u]);
    	st[u] = 1;
    	for (auto & v : adj[u]) {
    		if (!st[v]) {
    			self(self, v);
    		}
    	}
    };

    i64 ans = 0;
    for (int i = 0; i < n; ++ i) {
    	if (!st[i]) {
    		min = 2e9;
    		dfs(dfs, i);
    		ans += min;
    	}
    }

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

D. Credit Card

题意:一开始你有\(0\)元,每天会增加一些钱或者减少一些钱,或者询问当天的余额。你可以在任意一天充任意多的钱,你要保证任何时候余额都不超过\(d\),同时查询余额的时候余额不是负数。要求充钱次数最少。

如果我们需要充钱,那么最小把余额充到\(0\),最多充到\(d\)。那么我们可以维护一个上界和一个下界,每次查询的时候,如果需要充钱则把下界变为\(0\),上界变为\(d\)。其它情况就是两个值同时变化,如果上界大于\(d\),就变回\(d\),相当于我们在之前充钱的那一天少充一些,如果下界大于\(d\),则无解。

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

    int ans = 0;
    i64 l = 0, r = 0;
    for (auto & x : a) {
    	if (x == 0) {
    		if (l < 0) {
    			l = 0;
    		}

    		if (r < 0) {
    			r = d;
    			++ ans;
    		}
    	} else {
    		l += x;
    		r += x;
    		if (l > d) {
    			std::cout << -1 << "\n";
    			return;
    		}

    		if (r > d) {
    			r = d;
    		}
    	}
    }

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

E. Counting Arrays

题意:求长度为\(y\),乘积为\(x\)的序列有多少个,数组可以有负数。首先一个结论是,一个数的质因子不超过\(log_2n\)个。那么这个序列其它位置都是\(1\)。那么我们先对\(x\)质因数分解,那么对于每个因子,我们要把它们放到\(y\)个位置,每个位置可以为空,那么这就是插板法了。总共有\(C(cnt + y - 1, y - 1)\)种放法。每个质因子要单独考虑,因为不同质因子是不同的,而插板法要求每个元素没有区间。
然后考虑负数怎么放,显然\(x\)是正数,那么负数个数得是偶数个,那么对于\(y\)个数,我们选偶数个乘上\(-1\),,那么就是\(C(2, y) + C(4, y) + ...\),这个数是\(2^{y-1}\)次方。

代码省略取模类。

点击查看代码
struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;
    
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }
    
    void init(int m) {
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        
        for (int i = n + 1; i <= m; i++) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m;
    }
    
    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac(n) * invfac(m) * invfac(n - m);
    }
} comb;

void solve() {
	int x, y;
	std::cin >> x >> y;
	Z ans = 1;
	for (int i = 2; i * i <= x; ++ i) {
		if (x % i == 0) {
			int n = 0;
			while (x % i == 0) {
				++ n;
				x /= i;
			}
			ans *= comb.binom(n + y - 1, y - 1);
		}
	}

	if (x > 1) {
		ans *= comb.binom(y, y - 1);
	}

	ans *= power<Z>(2, y - 1);
	std::cout << ans << "\n";
}

F. Subtree Minimum Query

题意:给你一棵树,每次求\(x\)的子树里跟他距离不超过\(k\)的节点的权值的最小值。强制在线。

可以给每一个深度的点建主席树,然后可持久化。那么问题变成求深度为\([dep_x, dep_x + k]\)的且是\(x\)子树里的节点最小值。可以用\(dfs\)序使得子树变成一个连续的区间。不过似乎最小值没法做差,但发现我们查询的区间就限定了只有子树里的点才能查询到,所以直接单棵树查询就行了。

点击查看代码
#define ls(u) (tr[u].lson)
#define rs(u) (tr[u].rson)

const int N = 1e5 + 5;

struct Node {
	int lson, rson;
	int min;
}tr[N << 5];

int root[N];
int idx = 0;

void insert(int & u, int v, int l, int r, int p, int val) {
	u = ++ idx;
	tr[u] = tr[v];

	tr[u].min = std::min(tr[u].min, val);

	if (l == r) {
		return;
	}

	int mid = l + r >> 1;
	if (p <= mid) {
		insert(ls(u), ls(v), l, mid, p, val);
	} else {
		insert(rs(u), rs(v), mid + 1, r, p, val);
	}
}

int query(int u, int l, int r, int L, int R) {
	if (L <= l && r <= R) {
		return tr[u].min;
	}

	int mid = l + r >> 1;
	if (R <= mid) {
		return query(ls(u), l, mid, L, R);
	} else if (L > mid) {
		return query(rs(u), mid + 1, r, L, R);
	}

	return std::min(query(ls(u), l, mid, L, mid), query(rs(u), mid + 1, r, mid + 1, R));
}

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

    std::vector<std::vector<int>> adj(n + 1);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    std::vector<int> in(n + 1), out(n + 1), dep(n + 1);
    std::vector<std::vector<int>> d(n + 1);
    int dfn = 0;
    auto dfs = [&](auto & self, int u, int fa) -> void {
    	in[u] = ++ dfn;
    	d[dep[u]].push_back(u);
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}	

    		dep[v] = dep[u] + 1;
    		self(self, v, u);
    	}

    	out[u] = dfn;
    };


    dep[r] = 1;
    dfs(dfs, r, 0);
    int maxd = *std::max_element(dep.begin(), dep.end());
    tr[0].min = 2e9;
    for (int i = 1, last = 0; i <= maxd; ++ i) {
    	for (auto & x : d[i]) {
    		insert(root[i], last, 1, n, in[x], a[x]);
    		last = root[i];
    	}
    }

    int m;
    std::cin >> m;
    int last = 0;
    while (m -- ) {
    	int x, y;
    	std::cin >> x >> y;
    	x = (x + last) % n + 1, y = (y + last) % n;
    	last = query(root[std::min(maxd, dep[x] + y)], 1, n, in[x], out[x]);
    	std::cout << last << "\n";
    }
}
posted @ 2025-03-14 17:27  maburb  阅读(9)  评论(0)    收藏  举报