【题解】Codeforces Round 1081 (Div. 2)

Problem A. String Rotation Gam

  • "旋转"操作最多使得答案比旋转前多 1 1 1。
  • 如果原序列中有一个长度大于 1 1 1 的 "block"(相同类型字符组成的连续子字符串),并且首尾字母不同,就可以使得答案 + 1 + 1 +1。
  • 具体操作方法是,把长度大于 1 1 1 的 "block" 换到首尾。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;

void solve(){
	int n;
	cin >> n;
	string s;
	cin >> s;
	s = "#" + s;
	bool flag = false;
	int ans = 0;
	for(int i = 1; i <= n; i ++){
		int j = i;
		while(j + 1 <= n && s[j + 1] == s[i]) j ++;
		if(j - i > 0) flag = 1;
		ans ++;
		i = j;
	}
	if(s[1] != s[n] && flag) ans ++;
	cout << ans << '\n';
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

Problem B. Flipping Binary String

  • 同一个位置被翻转偶数次等价于不被翻转,被反转奇数次等价于翻转 1 1 1 次。题目中 "翻转除了 i 的所有位置" 这一操作,可以拆成两步独立的操作,先翻转一次所有位置(操作1),再翻转一次第 i 个位置(操作2)。
  • 如果原序列中 '1' 的个数是偶数,那么通过在所有 s [ i ] = 1 s[i] = 1 s[i]=1 的位置做翻转,可以使得序列变为全 '0'。此时,操作 1 1 1(翻转全部)的次数为偶,等于不做;而操作 2 2 2(翻转第 i i i 个)用在了每一个 s [ i ] = 1 s[i] = 1 s[i]=1的位置,使其变为 0。
  • 如果原序列中 '0' 的个数是奇数,那么通过在所有 s [ i ] = 0 s[i] = 0 s[i]=0 的位置做翻转,可以使得序列变为全 '0'。我们类比上一种情况理解,此时,操作 1 1 1(翻转全部)的次数为偶,等于全部做一次翻转,那么原来为 '0' 的位置变为 '1',需要对这些位置进行操作 2 2 2 使之变回 '0'。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;

void solve(){
	int n;
	cin >> n;
	string s;
	cin >> s;
	s = "#" + s;
	array<int,2> cnt = {0,0};
	for(int i = 1; i <= n; i ++){
		cnt[s[i] - '0'] ++;
	}
	vector<int> ans;
	if(cnt[1] % 2 == 0){
		for(int i = 1; i <= n; i ++){
			if(s[i] == '1') ans.push_back(i);
		}
	}else if(cnt[0] % 2 == 1){
		for(int i = 1; i <= n; i ++){
			if(s[i] == '0') ans.push_back(i);
		}
	}else{
		cout << -1 << '\n';
		return;
	}
	cout << ans.size() << '\n';
	for(int i : ans){
		cout << i << ' ';
	}
	cout << '\n';
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

Problem C. All-in-one Gun

  • 设 a a a 数组的和 ( ∑ i = 1 n a i \sum_{i = 1}^{n} a_i ∑i=1nai) 为 s u m sum sum,当 h ≥ s u m h \ge sum h≥sum 时,肯定能打完全部子弹,是否交换子弹不影响结果,只需要关注 h   m o d   s u m h \bmod sum hmodsum 的部分,该部分记为 l l l,即 l = h   m o d   s u m l = h \bmod sum l=hmodsum 。
  • 从前往后枚举每一颗子弹,找到第一个能消耗完 l l l 的位置。设当前位置的前缀和为 p r e s u m presum presum,如果不进行交换,那么要满足 p r e s u m ≥ l presum \ge l presum≥l;如果进行交换,选择前缀最小值和后缀最大值交换,要满足 p r e s u m − p r e m i + s u f f [ i + 1 ] ≥ l presum - premi + suff[i+1] \ge l presum−premi+suff[i+1]≥l, p r e m i premi premi 为前缀最小值, s u f f [ i + 1 ] suff[i+1] suff[i+1] 从第 i i i 位起的后缀最大值。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;

void solve(){
	int n,h,k;
	cin >> n >> h >> k;
	vector<int> a(n+1);
	for(int i = 1; i <= n; i ++) cin >> a[i];
	int sum = accumulate(a.begin()+1,a.end(),0ll);
	int l = h % sum;
	if(l == 0){
		cout << (k + n) * (h / sum) - k << '\n';
		return;
	}else{
		vector<int> suff(n+2);
		for(int i = n; i >= 1; i --){
			suff[i] = max(suff[i+1],a[i]);
		}
		int premi = 1e18,presum = 0;
		for(int i = 1; i <= n; i ++){
			premi = min(premi,a[i]);
			presum += a[i];
			if(presum >= l || presum - premi + suff[i+1] >= l){
				cout << (k + n) * (h / sum) + i << '\n';
				return;
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

Problem D. Cost of Tree

设点 i i i 的子树中各点的权重和为 s u m a [ i ] suma[i] suma[i]。

  1. 首先考虑不进行交换操作时,如何计算每个根节点 u u u 的答案:
    • 枚举与 u u u 相连的点 i i i。
    • 通过 d f s dfs dfs 得到点 i i i 的答案,将来自点 i i i 的答案记为 a n s [ i ] ans[i] ans[i],是叶子节点则答案为 0 0 0;
    • 来自点 i i i 子树的所有点到 u u u 的距离比到 i i i 的距离多 1 1 1,所以他们对 u u u 的贡献比对 i i i 多一个 s u m a [ i ] suma[i] suma[i]。代码上体现为 a n s [ u ] + = a n s [ i ] + s u m a [ i ] ans[u] += ans[i] + suma[i] ans[u]+=ans[i]+suma[i]
  2. 然后考虑在点 u u u 进行交换操作能产生的答案增量 a d d i t i o n [ u ] addition[u] addition[u]:
    • 原本点 i i i 子树可以做的操作,新的根也可做。 a d d i t i o n [ u ] = max ⁡ ( a d d i t i o n [ u ] , a d d i t i o n [ i ] ) addition[u] = \max{(addition[u],addition[i])} addition[u]=max(addition[u],addition[i])。
    • 贪心地把 s u m a suma suma 最大的子树整体割下,接到深度最深的节点上。如果深度最深的节点恰好在 s u m a suma suma 最大的子树中,则考虑第二深节点和第二大子树。

点 i i i 的最终答案为 a n s [ i ] + a d d i t i o n [ i ] ans[i] + addition[i] ans[i]+addition[i]。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;

void solve(){
	int n;
	cin >> n;
	vector<int> a(n+1);
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
	}
	vector<vector<int>> v(n+1);
	for(int i = 1; i <= n - 1; i ++){
		int x,y;
		cin >> x >> y;
		v[x].push_back(y),v[y].push_back(x);
	}
	vector<int> ans(n+1),addition(n+1);
	vector<int> suma(n+1),deep(n+1);
	auto dfs = [&](auto &&self,int u,int p) -> void{
		vector<pair<int,int>> tmpsum,tmpdeep;
		tmpsum.clear();
		tmpdeep.clear();
		for(int i : v[u]){
			if(i == p) continue;
			self(self,i,u);
			tmpsum.push_back({suma[i],i});
			tmpdeep.push_back({deep[i],i});
			suma[u] += suma[i];
			deep[u] = max(deep[u],deep[i] + 1);
			ans[u] += ans[i] + suma[i];
			addition[u] = max(addition[u],addition[i]);
		}
		suma[u] += a[u];
		sort(tmpsum.begin(),tmpsum.end());
		sort(tmpdeep.begin(),tmpdeep.end());
		if(tmpsum.size() <= 1){
			addition[u] = max(addition[u],0ll);
		}
		else if(tmpsum.back().second != tmpdeep.back().second){
			addition[u] = max(addition[u],tmpsum.back().first * (tmpdeep.back().first + 1));
		}else{
			addition[u] = max(addition[u],tmpsum[tmpsum.size()-2].first * (tmpdeep.back().first + 1));
			addition[u] = max(addition[u],tmpsum.back().first * (tmpdeep[tmpdeep.size()-2].first + 1));
		}
	};
	dfs(dfs,1,-1);
	for(int i = 1; i <= n; i ++){
		cout << ans[i] + addition[i] << " \n"[i == n];
	}
	
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

Problem E. Swap to Rearrange

  • 对于 1 ~ n 的每个数字,其分给 a a a 数组的数量和不分给 a a a 数组必须相同。如果把每个自然数看作一个点,这个结论可翻译为每个点的入度和出度相同。
  • 欧拉回路中,每个点的入度和出度相同。
  • 考虑在每一对 a [ i ] , b [ i ] a[i] , b[i] a[i],b[i] 之间建立一条无向边,找到一条合法的欧拉回路即可。
  • 需要前置知识------欧拉回路,P7771 【模板】欧拉路径
  • 无向图找欧拉回路的方法:将一条无向边拆为两条共享同一个 i d id id 的有向边,称这两条有向边又称为无向边的"半边"。找欧拉回路时,经过一条有向边就标记其 i d id id 为已使用,确保每条无向边只走过一次。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;
typedef pair<int,int> PII;

struct edge{
	int x,id,dir;
	edge(int _x,int _id,int _dir):x(_x),id(_id),dir(_dir){}
};

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

	vector<int> p(n+1),deg(n+1);
	
	
	vector<int> done(n+1);
	vector<vector<edge>> v(n+1);
	//0 a->b 
	for(int i = 1; i <= n; i ++){
		v[a[i]].emplace_back(b[i],i,1);
		v[b[i]].emplace_back(a[i],i,0);
		deg[a[i]] ++,deg[b[i]] ++;
	}

	for(int i = 1; i <= n; i ++){
		if(deg[i] % 2 != 0){
			cout << -1 << '\n';
			return;
		}
	}
	vector<int> ans,vis(n+1);

	auto dfs = [&](auto &&self,int u)->void{
		vis[u] = 1;
		while(p[u] < v[u].size()){
			auto [i,id,dir] = v[u][p[u] ++];
			if(done[id]) continue;
			done[id] = 1;
			if(dir) ans.push_back(id);
			
			self(self,i);
		}
	};

	for(int i = 1; i <= n; i ++){
		if(!vis[i]){
			dfs(dfs,i);
			
		}
	}
	
	cout << ans.size() << '\n';
	for(int i : ans){
		cout << i << ' ';
	}
	cout << '\n';
	
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}
相关推荐
白藏y2 小时前
【C++】muduo接口补充
开发语言·c++·muduo
xiaoye-duck3 小时前
《算法题讲解指南:递归,搜索与回溯算法--综合练习》--14.找出所有子集的异或总和再求和,15.全排列Ⅱ,16.电话号码的字母组合,17.括号生成
c++·算法·深度优先·回溯
OOJO3 小时前
c++---vector介绍
c语言·开发语言·数据结构·c++·算法·vim·visual studio
茉莉玫瑰花茶3 小时前
数据结构 - 并查集
数据结构
汀、人工智能3 小时前
05 - 函数基础
数据结构·算法·数据库架构·05 - 函数基础
HAPPY酷3 小时前
Python高级架构师之路——从原理到实战
java·python·算法
枫叶林FYL3 小时前
第9章 因果推理与物理理解
人工智能·算法·机器学习
Tanecious.3 小时前
蓝桥杯备赛:Day5-P1706 全排列问题
c++·蓝桥杯
胖咕噜的稞达鸭3 小时前
C++技术岗面试经验总结
开发语言·网络·c++·网络协议·tcp/ip·面试