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

A. Binary Imbalance

题意:给你一个01串,每次选一个01或者一个10在他们中间插一个0进去,问能不能让0的个数大于1。

我们进行一次插入操作后,显然还可以继续操作,所以只要有0和1就一定可以。注意特判全0的情况。

点击查看代码
void solve() {
   	int n;
   	std::cin >> n;
   	std::string s;
   	std::cin >> s;
   	if (s.find('1') == s.npos || s.find('0') != s.npos) {
   		std::cout << "YES\n";
   	} else {
   		std::cout << "NO\n";
   	}
}

B. Getting Points

题意:一共n天,你要在n天获得m分,让自己不被开除。每过七天会增加一个任务,每天可以上课,上课可以加x分,做任务可以加y分,每天只能上一次课,一次课可以最多做两个任务,如果每天都去上课也可以不被开除。但你想让自己休息的天数最多。

先计算有多少任务可以做,那么优先上课做两个任务,这样可以得 cnt / 2 * (x + 2 * y)分,如果m小于等于这个就直接输出 max(0, n - $\lceil $$\frac{m}{x+2*y}$$ \rceil$),否则先看剩下还有没有一个单独的任务,做完所有任务后判断还需要上多少天课就行。

点击查看代码
void solve() {
 	i64 n, m, x, y;
 	std::cin >> n >> m >> x >> y;
 	i64 cnt = (n + 6) / 7;
 	if (cnt / 2 * (x + 2 * y) < m) {
 		m -= cnt / 2 * (x + 2 * y);
 		i64 ans = cnt / 2;
 		if (cnt & 1) {
 			m -= x + y;
 			++ ans;
 		}

 		ans += std::max(0ll, (m + x - 1) / x);
 		std::cout << std::max(0ll, n - ans) << "\n";
 	} else {
 		std::cout << std::max(0ll, n - (m + x + 2 * y - 1) / (x + 2 * y)) << "\n";
 	}
}

C. Insert and Equalize

题意:给你一个数组,你要加一个不在这个数组里的数\(a_{n+1}\)进去,然后选一个x(x > 0),然后每个数不断加x到每个数相等。问最少加多少次。

对a排序后,x就是所有 \(a_i\) - \(a_{i-1}\)的最大公约数,因为它要满足能从\(a_{n-1}\)变到\(a_n\)那么x是\(a_{n-1}\)-\(a_n\)的因子,\(a_{n-2}\)变到\(a_n\)的过程中肯定要经过\(a_{n-1}\), 所以x是\(a_{n-1}\)-\(a_{n-2}\)的因子,同理,他应该是所有 \(a_i\) - \(a_{i-1}\)的最大公约数。\(a_{n+1}\)选一个最大的不在a里的x的倍数就行。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<i64> a(n);
    i64 max = -2e9;
    std::set<i64> s;
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	s.insert(a[i]);
    	max = std::max(max, a[i]);
    }

    if (n == 1) {
    	std::cout << 1 << "\n";
    	return;
    }

    std::sort(a.begin(), a.end());
    i64 d = 0;
    for (int i = 1; i < n; ++ i) {
    	d = std::gcd(d, a[i] - a[i - 1]);
    }

    d = std::abs(d);
    i64 an = max - d;
    for (i64 i = max - d; ; i -= d) {
    	if (!s.count(i)) {
    		an = i;
    		break;
    	}
    }
 
    i64 ans = (max - an) / d;
    for (int i = 0; i < n; ++ i) {
    	ans += (max - a[i]) / d;
    }

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

D. Robot Queries

题意:从(0, 0)开始移动,给你一个移动序列,然后q次询问,每次问你如果翻转[l, r]区间的操作,能不能经过(x, y)。

这题主要看你能不能看出翻转后坐标和翻转前坐标的关系。
注意到[1, l - 1]不变,然后因为x和y总移动的值不变,所以[r + 1, n]坐标不变。
那么对于[l, r]之间的坐标是怎么变化的? 画图发现应该是围着中心点旋转了180度。但具体坐标是多少?记\(p_i\)是不翻转执行i次操作后的下标,\(d_{ij}\)表示从i到j操作对\(x\)\(y\)的影响, 那么\(d_{ij}\) = \(p_j\) - \(p_{i-1}\)。那么如果(\(x\), \(y\))在翻转[l, r]这个区间后出现过,意味着有一个i满足(\(px_{l-1}\) + \(dx_ir\), \(py_{l-1}\) + \(dy_ir\)) = (\(x\), \(y\)), 那么就是(\(px_{l-1}\) + \(px_r\) - \(px_{i-1}\)\(py_{l-1}\) + \(py_r\) - \(py_{i-1}\)) = (\(x\), \(y\)), 得到 \(px_{i-1}\) = \(px_{l-1}\) + \(px_r\) - x, \(py_{i-1}\) = \(py_{l-1}\) + \(py_r\) - \(y\),也就是说,我们只要看没翻转之前[l - 1, r - 1]有没有(\(px_{l-1}\) + \(px_r\) - \(x\), \(py_{l-1}\) + \(py_r\) - \(y\))这个坐标就行了。
我是把询问换成三部分,问[0, l - 1]有没有出现(\(x\), \(y\)), [r, n]有没有出现(\(x\), \(y\)), [l - 1, r - 1]有没有出现(\(px_{l-1}\) + \(px_r\) - \(x\), \(py_{l-1}\) + \(py_r\) - \(y\))。
离线回答就行,用个map存每个坐标最后出现的地方。

点击查看代码
void solve() {
    int n, q;
    std::cin >> n >> q;
    std::string s;
    std::cin >> s;
    std::vector<std::pair<int, int> > path(n + 1);
    for (int i = 0; i < n; ++ i) {
    	path[i + 1] = path[i];
    	if (s[i] == 'U') {
    		++ path[i + 1].second;
    	} else if (s[i] == 'D') {
    		-- path[i + 1].second;
    	} else if (s[i] == 'L') {
    		-- path[i + 1].first;
    	} else {
    		++ path[i + 1].first;
    	}
    }

    std::vector<std::vector<std::array<int, 4> > > Q(n + 1);
    for (int i = 0; i < q; ++ i) {
    	int x, y, l, r;
    	std::cin >> x >> y >> l >> r;
    	Q[l - 1].push_back({x, y, 0, i});
    	Q[n].push_back({x, y, r, i});
    	Q[r - 1].push_back({path[l - 1].first + path[r].first - x, path[l - 1].second + path[r].second - y, l - 1, i});
    }

    std::map<std::pair<int, int> , int> idx;
    std::vector<int> ans(q);
    for (int i = 0; i <= n; ++ i) {
    	idx[path[i]] = i;
    	for (auto & [x, y, l, id] : Q[i]) {
    		if (idx.count({x, y}) && idx[{x, y}] >= l) {
    			ans[id] = 1;
    		}
    	}
    }

    for (int i = 0; i < q; ++ i) {
    	if (ans[i]) {
    		std::cout << "YES\n";
    	} else {
    		std::cout << "NO\n";
    	}
    }
}

E. Collapsing Strings

题意:两个字符串的C(x, y)为字符串x翻转后和y的最长公共前缀长度(lcp)。给你n个字符串求\(\sum_{i=1}^{n}\)\(\sum_{i=1}^{n}\)C(\(s_i\), \(s_j\))。

首先C(\(s_i\), \(s_j\)) = |\(s_i\)| + |\(s_j\)| - 2 \(\times\) lcp(\(s_j\), \(s_i\)\(^R\))。 \(s_i\)\(^R\)\(s_i\)翻转后的字符串。
我们对每个字符串单独考虑,只需要考虑每个字符串在前面或者后面的情况,不然就会算重。假设考虑字符串i在后面,那么i的贡献为\(\sum_{j=1}^{n}\) C(\(s_j\), \(s_i\))
= \(\sum_{j=1}^{n}\) |\(s_i\)| + |\(s_j\)| - 2 \(\times\) lcp(\(s_i\), \(s_j\)\(^R\))
= |\(s_i\)| \(\times\) n + \(\sum_{j=1}^{n}\)|\(s_j\)| - 2 \(\times\) \(\sum_{j=1}^{n}\)lcp(\(s_i\), \(s_j\)\(^R\))
那么前两个都是有的东西,我们只需要算2 \(\times\) \(\sum_{j=1}^{n}\)lcp(\(s_i\), \(s_j\)\(^R\))。
那么我们把每个\(s_i\)\(^R\)都插到字典树里取,那么就可以查询所有串的反串和\(s_i\)的公共前缀总和。

点击查看代码
struct Trie {
	std::vector<std::array<int, 26> > tr;
	std::vector<int> cnt;
	int idx;
	Trie() {
		tr.push_back({});
		cnt.push_back(0);
		idx = 0;
	}

	void insert(std::string s) {
		int p = 0;
		for (auto & c : s) {
			int x = c - 'a';
			if (!tr[p][x]) {
				tr[p][x] = ++ idx;
				tr.push_back({});
				cnt.push_back(0);
			}

			p = tr[p][x];
			cnt[p] += 1;
		}
	}

	i64 query(std::string s) {
		int p = 0;
		i64 res = 0;
		for (auto & c : s) {
			int x = c - 'a';
			if (!tr[p][x]) {
				return res;
			}

			p = tr[p][x];
			res += cnt[p];
		}

		return res;
	}
};

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::string> a(n);
    Trie tr;
    i64 sum = 0;
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	std::string t = a[i];
    	std::reverse(t.begin(), t.end());
    	tr.insert(t);
    	sum += a[i].size();
    }	

    i64 ans = 0;
    for (int i = 0; i < n; ++ i) {
    	ans += n * a[i].size() + sum - tr.query(a[i]) * 2;
    }
    std::cout << ans << "\n";
}
posted @ 2025-01-14 17:09  maburb  阅读(27)  评论(0)    收藏  举报