【题解】Codeforces Round 1097 (Div. 2, Based on Zhili Cup 2026) (致理杯) ABCDEF

A. Zhily and Array Operating

  • 贪心解决,当 ai+1<0a_{i+1} < 0ai+1<0 一定不操作,当 ai+1>0a_{i+1} > 0ai+1>0 一定操作,而且下标大的位置开始操作更优。
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];
	}
	for(int i = n - 1; i >= 1; i --){
		if(a[i+1] > 0){
			a[i] +=a [i+1];
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i ++){
		if(a[i] > 0) ans ++;
	}
	cout << ans << '\n';
}

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

B. Zhily and Mex and Max

  • 贪心解决,第一个位置放最大值,一定最优。
  • 后面的位置按mex递增放置即可,如果不能做到让mex递增,就随便放了。
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];
	}
	map<int,int> mp;
	for(int i = 1; i <= n; i ++){
		mp[a[i]] ++;
	}
	int mx = (--mp.end())->first;
	mp.rbegin()->second --;
	if(mp.rbegin()->second == 0) mp.erase((--mp.end()));
	

	int ans = 0,p = 0;
	set<int> se;
	se.insert(mx);
	while(se.find(p) != se.end()) p ++;
	ans += mx + p;
	for(int i = 2; i <= n; i ++){
		if(mp.find(p) != mp.end()){
			mp[p] --;
			se.insert(p);
			if(mp[p] == 0) mp.erase(mp.find(p));
		}
		else{
			mp.begin()->second --;
			se.insert(mp.begin()->first);
			if(mp.begin()->second == 0) mp.erase(mp.begin());
		}
		
		while(se.find(p) != se.end()) p ++;
		ans += mx + p;
	}
	cout << ans << '\n';
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

C. Zhily and Bracket Swapping

  • 左括号和右括号总数相等才可能有解。
  • 对于任意括号序列,如果某段前缀的右括号数量大于左括号,则不平衡。反之,每一个前缀的右括号数量小于左括号数量,且最后一个位置右括号数量等于左括号,则平衡。
  • 对于 ai=bia_i = b_iai=bi 的位置,交换无意义,直接加减。
  • 对于 ai≠bia_i \neq b_iai=bi 的位置,可以立刻贪心地把右括号换给当前左括号数多的一方(官解的做法),也可以把这种位置记录下来,存进 cntcntcnt,后续如果遇到一个序列不平衡,再决定把cntcntcnt里的左括号给不平衡的序列(本题解代码的做法)。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

using namespace std;

void solve(){
	
	int n;
	cin >> n;
	string s1,s2;
	cin >> s1 >> s2;
	s1 = "#" + s1;
	s2 = "#" + s2;
	int cntl = 0,cntr = 0;
	for(int i = 1; i <= n; i ++){
		if(s1[i] == '(') cntl ++;
		else cntr ++;
		if(s2[i] == '(') cntl ++;
		else cntr ++;
	}
	if(cntl != cntr){
		cout << "NO\n";
		return;
	}
	int sum1 = 0,sum2 = 0;
	int cnt = 0;
	for(int i = 1; i <= n; i ++){
		//cout << i << endl;
		if(s1[i] == s2[i]){
			if(s1[i] == '('){
				sum1 ++;
			}else{
				sum1 --;
			}
			
			if(s2[i] == '('){
				sum2 ++;
			}else{
				sum2 --;
			}
			
			if(sum1 < 0){
				if(cnt > 0){
					cnt -= 2;
					sum1 += 2;
				}
				else{
					cout << "NO\n";
					return;
				}
			}
			
			if(sum2 < 0){
				if(cnt > 0){
					cnt -= 2;
					sum2 += 2;
				}
				else{
					cout << "NO\n";
					return;
				}
			}
			
		}else{
			if(sum1 > 0 && sum2 > 0){
				cnt += 2;
				sum1 --;
				sum2 --;
			}else if(sum1 > 0){
				sum1 --;
				sum2 ++;
			}else if(sum2 > 0){
				sum2 --;
				sum1 ++;
			}else{
				if(cnt > 0){
					cnt -= 2;
					sum1 += 2;
					
					sum1 --;
					sum2 ++;
				}
				else{
					cout << "NO\n";
					return;
				}
			}
		}
		
	}
	
	cout << "YES\n";
}


signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

D. Zhily and Barknights

  • 独立计算任意两个位置是否引发争议。
  • 任选一对下标 (i,j)(i,j)(i,j) ,这对下标能引发争议当且仅当 biai>bjajb_ia_i >b_ja_jbiai>bjaj。
  • 如果我们枚举 bi,bjb_i,b_jbi,bj的值,将bi,bjb_i,b_jbi,bj的值固定,bibj\frac{b_i}{b_j}bjbi也就固定了,那么所有满足aiaj>bibj\frac{a_i}{a_j} >\frac{b_i}{b_j}ajai>bjbi 的 ai,aja_i,a_jai,aj都和这对 bi,bjb_i,b_jbi,bj 配对成功,引发一次争议。
  • 找到满足条件的 ai,aja_i,a_jai,aj 的方法是预处理所有 aiaj\frac{a_i}{a_j}ajai 后排序,再二分查找。

本题涉及的模板------分数类

cpp 复制代码
struct frac {
	int a, b;
	frac(int a, int b) : a(a), b(b) {}
	
	bool operator < (const frac& r) const {
		return (__int128)a * r.b < (__int128)r.a * b;
	}
	bool operator > (const frac& r) const {
		return (__int128)a * r.b > (__int128)r.a * b;
	}
	bool operator == (const frac& r) const {
		return (__int128)a * r.b == (__int128)r.a * b;
	}
	
	frac operator + (const frac& r) const {
		return {a * r.b + r.a * b, b * r.b};
	}
	frac operator - (const frac& r) const {
		return {a * r.b - r.a * b, b * r.b};
	}
	frac operator * (const frac& r) const {
		return {a * r.a, b * r.b};
	}
	frac operator / (const frac& r) const {
		return {a * r.b, b * r.a};
	}
};

完整代码

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

using namespace std;
typedef pair<int,int> PII;
const int mod = 998244353;

struct frac {
	int a, b;
	frac(int a, int b) : a(a), b(b) {}
	
	bool operator < (const frac& r) const {
		return (__int128)a * r.b < (__int128)r.a * b;
	}
	bool operator > (const frac& r) const {
		return (__int128)a * r.b > (__int128)r.a * b;
	}
	bool operator == (const frac& r) const {
		return (__int128)a * r.b == (__int128)r.a * b;
	}
	
	frac operator + (const frac& r) const {
		return {a * r.b + r.a * b, b * r.b};
	}
	frac operator - (const frac& r) const {
		return {a * r.b - r.a * b, b * r.b};
	}
	frac operator * (const frac& r) const {
		return {a * r.a, b * r.b};
	}
	frac operator / (const frac& r) const {
		return {a * r.b, b * r.a};
	}
};

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];
	}
	if(n == 1){
		cout << 0 << '\n';
		return;
	}
	auto cmp = [&](PII l,PII r) -> bool{
		int mul1 = l.first * r.second;
		int mul2 = r.first * l.second;
		return mul1 > mul2;
	};
	
	vector<frac> pa;
	for(int i = 1; i <= n; i ++){
		for(int j = i + 1; j <= n; j ++){
			pa.push_back({a[i],a[j]});
		}
	}
	
	pa.push_back({2000000000,1});
	sort(pa.begin(),pa.end(),greater<frac>());
	
	int cnt = 0;
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= n; j ++){
			if(i != j){
				int l = 0,r = pa.size() - 1;
				frac tmp = {b[j],b[i]};
				while(l != r){
					int mid = l + r + 1 >> 1;
					if(pa[mid] > tmp) l = mid;
					else r = mid - 1;
				}
				cnt += l ;
			}
		}
	}
	
	auto qmi = [&](int a,int b) -> int{
		int ret = 1;
		while(b){
			if(b & 1) ret = 1ll * a * ret % mod;
			a = 1ll * a * a % mod;
			b >>= 1;
		}
		return ret;
	};
	
	int pow1 = 1,pow2;
	for(int i = 1; i <= n-2; i ++){
		pow1 = pow1 * i % mod;
		pow1 %= mod;
	}
	pow2 = pow1 * (n-1) % mod * n % mod;
	pow2 %= mod;
	cnt %= mod;
	int ans = pow1 * cnt % mod * qmi(pow2,mod-2) % mod;
	
	cout << ans%mod << '\n';
}

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

E. Zhily and Signpost

  • 考虑用暴力做法解决本题。
  • 询问可以去重,多个相同时刻只计算一次。
  • 对于最简单的情况,所有模 dud_udu(节点儿子数)同余的询问,走的儿子是相同的,考虑聚合它们为询问组。
  • 定义一个询问组,为一系列 模 xxx 同余的数。对于一个询问组,如果 xxx 能被 dud_udu(节点儿子数)整除,或者组内只有一个数,那么整个询问组可以直接平移到同一个儿子,这个平移是 o(1)o(1)o(1) 的,使用 movemovemove 函数或指针。如果 xxx 不能被 dud_udu(节点儿子数)整除,那么进行一次暴力地拆分,枚举询问组内每一个询问,对其取模,重新归类到另一个模数更大的新询问组。
  • 看似暴力,实则已经可以过了,证明如下:
    • 对于所有平移操作,复杂度为 o(n)o(n)o(n)。
    • 得到的新询问组的模数 xxx,一定是老询问的两倍以上。而模数到达 1e181e181e18 之后,询问组内只有一个询问,不用再拆分。故拆分操作不超过 log⁡1e18\log{1e18}log1e18 次,每次拆分的时间复杂度为 o(n)o(n)o(n)。
cpp 复制代码
#include<bits/stdc++.h>
#define int long long

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

void solve(){
	int n,q;
	cin >> n >> q;
	vector<vector<int>> v(n+1);
	for(int i = 2; i <= n; i ++){
		int fa;
		cin >> fa;
		v[fa].push_back(i);
	}
	for(int i = 1; i <= n; i ++){
		sort(v[i].begin(),v[i].end());
	}
	
	vector<int> len(n+1);
	for(int i = 2; i <= n; i ++){
		cin >> len[i];
	}
	vector<int> ans(q+1);
	vector<PII> query(q+1);
	for(int i = 1; i <= q; i ++){
		cin >> query[i].first;
		query[i].second = i;
	}
	sort(query.begin()+1,query.end());
	vector<int> val;
	vector<vector<int>> idx;
	for(int i = 1; i <= q; i ++){
		val.push_back(query[i].first);
		idx.push_back({});
		idx.back().push_back(query[i].second);
		while(i + 1 <= q && query[i+1].first == val.back()){
			idx.back().push_back(query[i+1].second);
			i ++;
		}
	}
	
	vector<int> ask(idx.size());
	iota(ask.begin(),ask.end(),0ll);
	
	auto lcm = [&](__int128 a,__int128 b) -> __int128{
		__int128 g = a * b / __gcd(a,b);
		return g;
	};
	
	auto dfs = [&](auto &&self,int u,int p,int t,int mod,vector<int> a) -> void{
	
		//cout << u << endl; 
		
		if(v[u].size() == 0){
			for(int i : a){
				for(int j : idx[i]){
					ans[j] = u;
				}
			}
			return;
		}
		
		if(a.size() <= 1 || mod == -1 || mod % v[u].size() == 0){
			int ne = (val[a[0]] + t) % v[u].size();
			self(self,v[u][ne],u,t + len[v[u][ne]],mod,move(a));
		}else{
			vector<vector<int>> b(v[u].size());
			for(int i : a){
				b[(val[i] + t)%(v[u].size())].push_back(i);
			}
			
			int nmod = lcm(mod,v[u].size());
			if(nmod > 1e18) nmod = -1;
			
			for(int i = 0; i < v[u].size(); i ++){
				if(b[i].size()){
					self(self,v[u][i],u,t + len[v[u][i]],nmod,b[i]);
				}
			}
		}
	};
	
	dfs(dfs,1,-1,0,1,ask);
	for(int i = 1; i <= q; i ++){
		cout << ans[i] << " \n"[i == q];
	}
	
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t = 1;
	cin >> t;
	while(t --){
		solve();
	}
}

F. Zhily and Cycle

  • 肯定要转成欧拉回路求解,不然炸复杂度了,我们把每个点的出边(i,au)(i,a_u)(i,au)转化成(i,ai),(ai,au)(i,a_i),(a_i,a_u)(i,ai),(ai,au)两条边,找到新图的一个欧拉回路,再删掉第二条边就得到哈密顿回路。
  • 先把每个 iii 连上第一部分的边 (i,ai)(i , a_i)(i,ai)。
  • 如果对于每一个当前入度出度不平衡的点,恰好能找到n条边使得整个图入度出度平衡就有解了。
  • 观察所有入度大于出度的点,假设差值为degdegdeg,它们恰好能连degdegdeg条边,每条边能且只能连向序号大于它的点。因为他们入度大于出度本身是由(i,a_i)导致的,后面必定要跟一条(a_i,a_u),(a_i,a_u)这条边的要求有且仅有下一个点的序号更大。
  • 我们把(a_i,a_u)称作前向边,贪心解决这个问题。
  • 定义 deg[i]deg[i]deg[i] 为点 iii 的入度和出度的差值。 对 degdegdeg 求前缀和 perperper,一旦出现 pre[i]<0pre[i] < 0pre[i]<0 则无解。每一个 pre[i]==0pre[i] == 0pre[i]==0的位置成为切分点,因为不能有前向边跨过 pre[i]==0pre[i] == 0pre[i]==0 的位置,一旦跨过 pre[i]pre[i]pre[i] 减为负。
  • 对于两个 pre[i]==0pre[i] == 0pre[i]==0 所夹的区间,先贪心地连 (i,i+1),最后一个位置不加边,保证块内联通,然后把 deg[i]>0deg[i]>0deg[i]>0 的点记下来,作为提供者,遇到deg[i]<0deg[i]<0deg[i]<0 就连一条提供者到该点的前向边。
  • 如果新图不连通输出no。
  • 用模板求新图的欧拉回路。只保留欧拉回路中(i,a_i)这类边的左端点,就得到了答案。

本题涉及的模板------欧拉回路(保留边+保留点)

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

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

void Hierholzer_node(){
	int n;
	vector<vector<int>> g(n + 1);
	vector<int> p(n + 1);   // 当前点扫到第几条边
	vector<int> path;          
	
	auto dfs = [&](auto &&self, int u) -> void {
		while (p[u] < g[u].size()) {
			int v = g[u][p[u]++];
			self(self, v);
		}
		path.push_back(u);     
	};
	int st; 
	dfs(dfs, st);
	reverse(path.begin(), path.end());
}

void Hierholzer(){
	int n;
	cin >> n;
	vector<vector<PII>> v(n + 1); // v[l] = {r, eid}
	vector<int> from, to,used;                       
	int eid = 0;
	
	auto addedge = [&](int l, int r) -> int {
		v[l].push_back({r, eid});
		// 有向图删掉下面一行
		v[r].push_back({l, eid}); // 有向图删该行
		from.push_back(l);
		to.push_back(r);
		used.push_back(0);        
		return eid++;
	};

	vector<int> p(n + 1),edge,node; 
	
	auto dfs = [&](auto &&self, int u, int peid) -> void {
		while (p[u] < (int)v[u].size()) {
			auto [r, eid] = v[u][p[u]++];
			if (used[eid]) continue;
			used[eid] = 1;
			self(self, r, eid);
		}
		edge.push_back(peid); 
		node.push_back(u);      
	};
	
	int st;
	dfs(dfs, st, -1);
	// node 即点序列
	// edge 即边序列(edge[0] 为 -1)
	reverse(edge.begin(), edge.end());
	reverse(node.begin(), node.end());

}

完整代码

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

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

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);
	struct edge{
		int l,r,fix;
	};
	vector<edge> e;
	int np = 0;
	vector<int> deg(n+1);
	auto addedge = [&](int l,int r,int fix)->void{
		deg[l] --;
		deg[r] ++;
		e.push_back({l,r,fix});
		v[l].push_back(np);
		np ++; 
	};
	

	for(int i = 1; i <= n; i ++){
		addedge(i,a[i],1);
	}
	vector<int> pre(n+1);
	int last = 1;
	auto work = [&](int l,int r)->void{
		for(int i = l; i <= r - 1; i ++){
			addedge(i,i+1,0);
		}
	
		vector<PII> stk;
		for(int i = l; i <= r; i ++){
			if(deg[i] > 0){
				stk.push_back({i,deg[i]});
			}
			else if(deg[i] < 0){
				while(deg[i] < 0){
					addedge(stk.back().first,i,0);
					stk.back().second --;
					if(!stk.back().second) stk.pop_back();
				}
			}
		}
	};
	
	for(int i = 1; i <= n ; i ++){
		pre[i] = pre[i-1] + deg[i];
		if(pre[i] < 0){
			cout << "No\n";
			return;
		}
		if(pre[i] == 0){
			work(last,i);
			last = i + 1;
		}
	}
	
	vector<int> done(n+1);
	int cnt = 0;
	auto dfs = [&](auto &&self,int u)->void{
		done[u] = 1;
		cnt ++;
		for(int i : v[u]){
			if(!done[e[i].r]){
				self(self,e[i].r);
			}
		}
	};
	dfs(dfs,1);
	if(cnt != n){
		cout << "No\n";
		return;
	}
	
	vector<int> p(n+1);
	vector<int> euler;
	auto dfs2 = [&](auto &&self,int u,int id)->void{
		while(p[u] < v[u].size()){
			int eid = v[u][p[u]++];
			self(self,e[eid].r,eid);
		}
		euler.push_back(id);
	};
	dfs2(dfs2,1,-1);
	reverse(euler.begin(),euler.end());
	vector<int> ans;
	for(auto i : euler){
		if(i != -1 && e[i].fix){
			ans.push_back(e[i].l);
		}
	}
	cout << "Yes\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();
	}
}
相关推荐
IJCAST1 小时前
Exploring the Frontiers of Complexity: Latest Research from IJCAST
人工智能·深度学习·神经网络·算法
wuweijianlove1 小时前
算法优化中的控制流重构与分支预测机制的技术7
算法·重构
Yuezero_1 小时前
Latent Manifold理论分析
人工智能·算法·机器学习
山北雨夜漫步1 小时前
LangGraph
java·前端·算法
李佳鹏2 小时前
96% 成功率,零标注数据:我用 PCA + Hungarian 解了这道几何题
算法
华盛AI2 小时前
AI大模型竞品Anthropic Claude Opus 4.7深度分析
人工智能·算法
shehuiyuelaiyuehao2 小时前
算法21,搜索插入位置
python·算法·leetcode
Lazionr2 小时前
【栈与队列经典OJ】
c语言·数据结构
夏日听雨眠2 小时前
数据结构(哈希函数)
数据结构·算法·哈希算法