Codeforces Round 993 (Div. 4)题解

Codeforces Round 993 (Div. 4)
现在div4出那么难干嘛...
A. Easy Problem

输入一个数n,返回有多少组正整数(a,b) 满足a+b=n

每个数可以选择[1,n-1]共计n-1对。

void solve()
{
	int n = 0;
	cin >> n;
	cout << n - 1 << endl;
}

B. Normal Problem

玻璃上正面写一串pqw组成的字符串,输出背面看过去的字符串

翻转字符串,然后还要把pq对应替换

void solve()
{
	string s;
	cin >> s;
	reverse(s.begin(), s.end());
	for (auto& v : s) {
		if (v == 'p') v = 'q';
		else if (v == 'q') v = 'p';
	}
	cout << s << endl;
}

C. Hard Problem

有两排m个座位安排给猴子坐,a个猴子只能坐第一排,b个猴子只能做第二排,c个猴子都可以。问最多安排多少个猴子坐下

贪心 第一排坐 min(m,a)个,第二排坐min(m,b)个,然后剩下的尽量放c

void solve()
{
	int m, a, b, c;
	cin >> m >> a >> b >> c;
	int c1 = min(m, a);
	int c2 = min(m, b);
	int c3 = min(m - c1 + m - c2, c);
	cout << c1 + c2 + c3 << endl;
}

D. Harder Problem

给定长度为n的数组A,1<=Ai<=n,要求构建数组B,满足每个Ai是区间[B1Bi]的众数(或之一)
比如A=[1,1,2],则B可以是[1,2,2]

由于只要满足是众数之一即可,显然可以构建B为一个n的排列即可,每个数字出现次数都是1,只需要在Ai出现的时B中已经出现过Ai即可。
处理时要做到,每个Ai出现时B数组中i之前已经出现过Ai,还得注意i之后出现的Ai不能在B中使用。
那么可以在每个数字v首次出现的位置直接标记ans[i]=v,然后标记v已经使用flag[v]=1
然后从头遍历ans中为标记的位置,另一个指针就从1开始累加,找到第一个flag[v]为标记的数字赋值即可。

void solve()
{
	int n; cin >> n;
	vector<int> flag(n + 1);
	vector<int> ans(n);
	for (int i = 0; i < n; i++) {
		int v; cin >> v;
		if (!flag[v]) {
			flag[v] = 1;
			ans[i] = v;
		}
	}
	int now = 1;
	for (int i = 0; i < n; i++) {
		if (ans[i] == 0) {
			while (flag[now]) now++;
			ans[i] = now++;
		}
	}
	for (int i = 0; i < n; i++) cout << ans[i] << " ";
	cout << endl;
}

E. Insane Problem

给定k,x1,x2,y1,y2,要求统计(x,y)数对个数,要求满足
x1<=x<=x2
y1<=y<=y2
y/x=kn 这条意思是y/x是k的某个幂。比如k=2,y=8,x=2,则y/x=4=22,注意y/x必须是整除

这里n可以为0,考虑n=0的情况,此时x=y,即求[x1,x2]和[y1,y2]的重叠区间
求重叠区间有个常识做法,即重叠部分区间设为[L,R],则有
L=max(x1,y1)
R=min(yx,y2)
如果L>R则无重叠部分。
由于n为指数,可以暴力枚举n∈[0,60],对于n>0的情况,考虑x的值域,类似地,有
r=kn
L=max(x1,(y1+r1)/r) 注意在x值域中,要取能>=y1的x值
R=min(x2,y2/r)

void solve()
{
	long k, x1, x2, y1, y2;
	cin >> k >> x1 >> x2 >> y1 >> y2;
	long ans = 0;
	for (int n = 0; n <= 60; n++) {
		long r = pow(k, n);
		if (r * x1 > y2) break;
		ans += max(0L, min(x2, y2 / r) - max(x1, (y1 + r - 1) / r) + 1);
	}
	cout << ans << endl;
}

F. Easy Demon Problem

给定n长度数组A,m长度数组B,n*m矩阵的每一项为AiBj,设矩阵元素和为S=i=1nj=1mAiBj
你可以在一次操作中,可以将M某一行和某一列同时设置为0。
有q次查询,每次查询给出一个整数xi,问能否通过一次操作使得矩阵元素和S=xi,每次查询独立。
1<=n,m,x<=2e5

认真思考这个数据范围,n,m均为2e5,且没有标准n*m的范围,说明实际上无法枚举整个矩阵。只能单独枚举AB数组。
而x<=2e5,启发我们通过x的值处理。
在无操作的时候有S=AB。而一次操作实际上对应了从AB中各删除一个数据后的结果。即
xi=(Ax)(By)
请务必意识到这是一个将整数xi分解因子的式子
具体来说,预处理所有的(Ax)(By) 存入哈希表
分解xi,对于每组因子,计算能否一一对应。
注意xi可能为负数,且分解也有正负两种情况,以及互换AB的可能。
unordered_set会被卡哈希,不行就数组吧,或者自定义哈希函数。

void solve()
{
	i64 n, m, q; cin >> n >> m >> q;
	i64 sumA = 0, sumB = 0;
	vector<i64> A(n);
	for (int i = 0; i < n; i++) cin >> A[i], sumA += A[i];
	vector<i64> B(m);
	for (int i = 0; i < m; i++) cin >> B[i], sumB += B[i];
	set<i64> setA, setB;
	for (int i = 0; i < n; i++) setA.insert(sumA - A[i]);
	for (int i = 0; i < m; i++) setB.insert(sumB - B[i]);

	auto dfs = [&](i64 x) -> bool {
		if (x == 0) {
			if (setA.count(0) || setB.count(0)) return true;
		}
		i64 y = abs(x);
		for (int i = 1; i <= y / i; i++) {
			if (y % i == 0) {
				if (setA.count(i) && setB.count(x / i)) return true;
				if (setA.count(-i) && setB.count(-x / i)) return true;
				if (setB.count(i) && setA.count(x / i)) return true;
				if (setB.count(-i) && setA.count(-x / i)) return true;
			}
		}
		return false;
	};
	while (q-- > 0) {
		i64 x; cin >> x;
		if (dfs(x)) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
}

G1. Medium Demon Problem (easy version)

给定长为n的数组,每个元素ai表示有一条从i>ai的有向边,初始每个节点拥有一个礼物,每一轮每个节点会向子节点派发这个礼物。如果某个节点拥有超过1个,会立即变为1个。所有的派发同时发生。
问最少经过多少轮,所有节点的礼物数不再发生变化。

翻译完其实是很明显的内向基环树
然后求的是不在环上的点,距离环的最远距离。普通模拟大致是求出所有环上节点,然后倒序BFS查找最远距离。
第一轮是初始状态,经过最远的链的长度轮数后,到达环上,然后下一轮开始循环。
还是给个简易示意图。如下,节点9的礼物经过最多轮数到达环上,9->8->7->1,所以答案是3+2=5。

更简洁的做法是拓扑排序计算拓扑轮数即可。
特别注意,环上延伸出来的并非是一定是链,也可能是树,注意始终取子树里的最长链

void solve()
{
	int n; cin >> n;
	vector<int> deg(n);
	vector<int> fa(n);
	for (int u = 0; u < n; u++) {
		int v; cin >> v;
		v--;
		deg[v]++;
		fa[u] = v;
	}
	int step = 2;
	queue<int> queue;
	for (int i = 0; i < n; i++)  if (deg[i] == 0) queue.push(i);
	while (queue.size() > 0) {
		int size = queue.size();
		while (size-- > 0) {
			int u = queue.front();
			queue.pop();
			int v = fa[u];
			deg[v]--;
			if (deg[v] == 0) queue.push(v);
		}
		step++;
	}
	cout << step << endl;
}

G2. Medium Demon Problem (hard version)

与G1的区别在于 过程中节点个数可以>1,而每次只能向上派发一个,所以需要把当前节点所有子树下的礼物逐个派发完成才行
所以向上返回时需要附加当前子树size。

void solve()
{
	int n; cin >> n;
	vector<int> deg(n);
	vector<int> fa(n);
	for (int u = 0; u < n; u++) {
		int v; cin >> v;
		v--;
		deg[v]++;
		fa[u] = v;
	}
	queue<int> queue;
	vector<int> gifts(n);
	for (int i = 0; i < n; i++) {
		if (deg[i] == 0) queue.push(i);
		gifts[i] = 1;
	}
	int ans = 0;
	while (queue.size() > 0) {
		int size = queue.size();
		while (size-- > 0) {
			int u = queue.front();
			ans = max(ans, gifts[u]);
			queue.pop();
			int v = fa[u];
			deg[v]--;
			gifts[v] += gifts[u];
			if (deg[v] == 0) queue.push(v);
		}
	}
	cout << ans + 2 << endl;
}

基环树只有一个环
拓扑排序的处理过程中 依然可以计算子树size并向上返回,但是处理顺序是拓扑排序的顺序。

H. Hard Demon Problem

给定n行m列的矩阵,q个查询,每个查询里选择矩阵里的子区域,[x1,x2,y1,y2],然后把子区域扁平化为一个数组A
可知数组长度为c=(x2x1+1)(y2y1+1)
计算i=1cAii
此处必须附上出题人的灵魂画图:

这很Educational啊,先从一维去理解,给定长为n的数组A
从中任取区间[L,R],计算i=LRAi(iL+1)
比如数组[1,2,3,4,5,6],区间[L,R]=[3,5],截取的子数组为[3,4,5]
然后计算S=3 * 1 + 4 * 2 + 5 * 3 = 26。
当有q次查询时,我们如何快速的计算呢?
这是一个很好的公式推导教学
S=i=LRAi(iL+1) ->
S=i=LRAiiAi(L1)
左侧是Aii 右侧是Ai乘一个常数。
计算区间和常用策略是前缀和O(1)计算
很自然地,我们设sumA表示数组前i项之和,设sumAi表示数组前i项与i的乘积和,则有
S=sumAiRsumAiL1(sumARsumAL1)(L1)
对于[3,4,5]的案例,等价的,S=33+44+55(3+4+5)2=26

下面我们回到原题的二维情况
S=i=1cAii 我们需要把i与子区域的索引转换关系
S=i=x1x2j=y1y2Mij((ix1)(y2y1+1)+jy1+1) 前面的二维求和 我们简写为
S=Miji(y2y1+1)Mij(x1(y2y1+1)+y11)+Mijj
到这里我们得到了一个二维求和的公式,观察到为了计算S,我们需要预处理三个二维前缀和。分别是
Miji, Mijj 和原始的Mij
对于每个二维前缀和 我们分别计算子区域的和 然后按公式相加即可。
下面贴完整代码

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
#define vi vector<i64>
#define vvi vector<vi>
void solve()
{
	int n, q; cin >> n >> q;
	vvi nums(n);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			i64 v; cin >> v;
			nums[i].push_back(v);
		}
	}
	vvi sum(n + 1, vi(n + 1)), sum_i(n + 1, vi(n + 1)), sum_j(n + 1, vi(n + 1));
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + nums[i - 1][j - 1];
			sum_i[i][j] += sum_i[i - 1][j] + sum_i[i][j - 1] - sum_i[i - 1][j - 1] + nums[i - 1][j - 1] * i;
			sum_j[i][j] += sum_j[i - 1][j] + sum_j[i][j - 1] - sum_j[i - 1][j - 1] + nums[i - 1][j - 1] * j;
		}
	}
	auto sumRegion = [&](vvi& sum, int x1, int x2, int y1, int y2) -> i64 {
		return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
		};
	vi ans;
	while (q-- > 0) {
		int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
		i64 s1 = sumRegion(sum_i, x1, x2, y1, y2) * (y2 - y1 + 1);
		i64 s2 = sumRegion(sum, x1, x2, y1, y2) * (x1 * (y2 - y1 + 1) + y1 - 1);
		i64 s3 = sumRegion(sum_j, x1, x2, y1, y2);
		ans.push_back(s1 - s2 + s3);
	}
	for (auto v : ans) cout << v << " ";
	cout << endl;
}
int main() {
	std::ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int t = 1;
	cin >> t;
	while (t-- > 0) {
		solve();
	}
}
posted @   云上寒烟  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示