Codeforces Round 1065 (Div. 3)

Dashboard - Codeforces Round 1065 (Div. 3) - Codeforces

C1&C2

\(S_a=\oplus_{i=1}^{n}{a_i},S_b=\oplus_{i=1}^{n}{b_i}\),那么我们知道\(S=S_a\oplus S_b\)是一个定值,这是一个关键性质,我们可以根据这个讨论\(S\)二进制的每一位:

  • 如果\(S_i=0\),那么也就是不论如何交换\(a_j\)\(b_j\),最后\(S_{a,i}=S_{b,i}\)
  • 如果\(S_i=1\),那么也就是\(S_{a,i}\ne S_{b,i}\),那么最后这一位肯定不同,而且可以知道的是,第\(i\)位仅由最大的\(j\in[1,n]\and ((a_j\oplus b_j)>>i)\&1\)决定,这个是很显然的;

所以最后判断得分,我们需要找到\(S\)最高位的\(1\),然后找到\(j\),如果\(j\equiv 1\mod 2\),那么赢家是Ajisai,否则是Mai

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;

void solve() {
	int n;
	cin >> n;
	vector<int> a(n+1),b(n+1);
	int S=0;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		S^=a[i];
	}
	for(int i=1;i<=n;i++){
		cin >> b[i];
		S^=b[i];
	}
	if(S==0){
		cout << "Tie" << endl;
		return;
	}
	int pos=-1;
	for(int i=31;i>=0;i--){
		if(S>>i&1){
			pos=i;
			break;
		}
	}
	int j=0;
	for(int i=1;i<=n;i++){
		if((a[i]^b[i])>>pos&1){
			j=i;
		}
	}
	if(j&1) cout << "Ajisai" << endl;
	else cout << "Mai" << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

D&F

题意是给定排列\(p\),如果\(1\le u <v \le n\),那么当且仅当\(pos_u<pos_v\),也就是如果\(u\)\(v\)连边,那么需要\(u\)\(p\)中的位置\(pos_u\)小于\(v\)\(p\)中的位置\(pos_v\),问对于给定的排列\(p\),是否可以可以构成一棵树,也就是判断所有点是否可以连通。\(F\)是在\(D\)的基础上,还需要输出构造的树。

主播赛时写这道题的时候用的并查集,就是很明显的我们可以从后往前分成一些单调增的连通块,块内都是连通的,而且我们可以在块内选出代表元素,之后我们考虑如何合并没有连通的块。这样是错的,而且非常难写,主播刚刚对拍出了一个错误数据:

1
13
11 1 9 7 5 8 4 12 13 3 6 10 2

\(sol\):

我们应该考虑维护一个连通集合\(S\),同时记录\(S\)的最小值\(mn\),同时用一棵权值线段树\(tr\)(可以支持单点修改和区间查询\(max\)),还需要一个\(pos_v\)\(fa_v\)数组,\(pos_v\)表示值为\(v\)的位置,\(fa_v\)表示值为\(v\)的父亲,最后\(vis_i\)表示第\(i\)个位置是否已经加入集合\(S\)

之后枚举新的元素\(p_i\),按照下面步骤扩充\(S\)

  • 当集合\(S\)为空时,将第一个元素加入\(S\),更新最小值\(mn\),将\(v\in[mn+1,n]\)的所有元素加入集合\(S\),同时单点更新\(tr\),即\(tr.update(v,v,pos_v)\),标记\(vis_i=1\)
  • 如果\(vis_i=1\),那么跳过;
  • 否则我们考虑加入\(p_i\),可以知道的是\(p_i<mn\),这是肯定的,因为在上一步,我们已经把\(p_i>mn\)的元素都处理了。之后我们查询\(r=tr.query(mn+1,n)\),如果\(r<i\),那么肯定\(p_i\)无法加入集合\(S\),那么输出NO;如果\(r>i\),将\([p_i+1,mn-1]\)的元素都连向\(p_i\),同时更新\(tr.update(v,v,pos_v)\),标记\(vis_i=1\),最后记得将\(p_i\)连向\(p_r\),之后更新\(mn\)

如果一直没有输出NO,那么输出YES,同时打印这棵树。

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
#define lc u<<1
#define rc u<<1|1
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
struct Segment {
	struct node {
		int l, r, sum;
	};
	vector<int> vc;
	vector<node> seg;
	Segment() {}
	Segment(int n) {
		vc.resize(n + 10);
		seg.resize((n << 2) + 10);
	}
	void pushup(int u) {
		seg[u].sum = max(seg[lc].sum ,seg[rc].sum);
	}
	
	void build(int u, int l, int r) {
		seg[u] = {l, r, 0};
		if (l == r) return;
		int mid = (l + r) / 2;
		build(lc, l, mid), build(rc, mid + 1, r);
		pushup(u);
	}
	
	//区间修改
	void update(int u, int l, int r, int z) {
		if (seg[u].l >= l && seg[u].r <= r) {
			seg[u].sum = max(seg[u].sum,z);
			return;
		}
		int mid = (seg[u].l + seg[u].r) / 2;
		if (l <= mid) update(lc, l, r, z);
		if (r > mid) update(rc, l, r, z);
		pushup(u);
	}
	
	//区间查询
	int query(int u, int l, int r) {
		if (seg[u].l >= l && seg[u].r <= r) {
			return seg[u].sum;
		}
		int res = 0LL;
		int mid = (seg[u].r + seg[u].l) / 2;
		if (l <= mid) res =max(res, query(lc, l, r));
		if (r > mid) res = max(query(rc, l, r),res);
		return res;
	}
};

void solve() {
	int n;
	cin >> n;
	vector<int> p(n+2);
	vector<int> pos(n+1);
	vector<int> fa(n+1);
	for(int i=1;i<=n;i++){
		cin >> p[i];
		pos[p[i]]=i;
	}
	//维护集合S,需要S中的最小元素mn,以及S中的最大元素位置
	Segment tr(n);
	tr.build(1,1,n);
	int mn=p[1];
	vector<int> vis(n+1);
	for(int v=mn+1;v<=n;v++){
		tr.update(1,v,v,pos[v]);
		fa[v]=mn;
		vis[pos[v]]=1;
	}
	for(int i=2;i<=n;i++){
		if(vis[i]==1) continue;
		//p[i]<mn,mx>p[i],要求tr.query(mn+1,n)>i
		int r=tr.query(1,mn+1,n);
		if(r<i){
			cout << "NO" << endl;
			return;
		}
		fa[p[i]]=p[r];
		for(int v=p[i]+1;v<mn;v++){
			tr.update(1,v,v,pos[v]);
			fa[v]=p[i];
			vis[pos[v]]=1;
		}
		mn=min(mn,p[i]);
	}
	cout << "YES" << endl;
	for(int i=2;i<=n;i++){
		cout << p[i] << " " << fa[p[i]] << endl;
	}
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

E

构造题。

要构造长度为\(n\)的排列,且坏索引个数小于等于6

主播自己的想法是枚举质数,然后双指针来写。有个很显然的贪心,我们从大到小枚举质数\(p\),把\(p\mid i\)\(i\)加入\(p\)的集合,因为对于一个数\(i\),可能有两个质数\(p_1,p_2\mid i\and p_1<p_2\),因为\(p_1<p_2\),所以\(n/p_1\ge n/p_2\),所以先把\(i\)分给\(p_2\)是更优的,然后用\(i\)枚举质数\(p_i\),同时一个指针\(j\)\(p_0\)开始,分下面情况讨论:

  • 如果\(fac[p_i].size()>1\),那么直接输出\(fac[p_i]\)
  • 如果\(fac[p_i].size()=1\),那么先输出\(fac[p_i][0]\),之后再来两个\(fac[p_j]\)的元素;

这样当\(n\leq 3000\)时都还是比较正确的,当\(n=3442\)时,按照主播这种构造方法会有8个坏索引。但是我感觉主播这个还可以优化一下,可是没有坏索引的上界证明,所以还是不采取这种做法。

来看看\(std\)

考虑偶数,有\(n/2\)个,那么每两个配合一个其他数,大概可以用掉\(3n/4\)个,之后我们再用3的奇数倍个数来构造,大概有\(n/6\)个,每两个配合一个,所以有\(n/6+n/12=n/4\)个,配合上前面大概有\(n\)个,有证明保证这个构造方法的坏索引不会超过4个。

void solve() {
	int n;
	cin >> n;
	vector<int> p1,p2;
	vector<int> vis(n+1);
	for(int i=1;i*2<=n;i++){
		p1.push_back(i*2);
		vis[i*2]=1;
	}
	for(int i=1;i*3<=n;i+=2){
		p2.push_back(i*3);
		vis[i*3]=1;
	}
	vector<int> re;
	for(int i=1;i<=n;i++){
		if(!vis[i]) re.push_back(i);
	}
	vector<int> ans;
	while(re.size()&&p1.size()>=2){
		ans.push_back(re.back());
		re.pop_back();
		ans.push_back(p1.back());
		p1.pop_back();
		ans.push_back(p1.back());
		p1.pop_back();
	}
	while(re.size()&&p2.size()>=2){
		ans.push_back(re.back());
		re.pop_back();
		ans.push_back(p2.back());
		p2.pop_back();
		ans.push_back(p2.back());
		p2.pop_back();
	}
	while(p1.size()){
		ans.push_back(p1.back());
		p1.pop_back();
	}
	while(p2.size()){
		ans.push_back(p2.back());
		p2.pop_back();
	}
	while(re.size()){
		ans.push_back(re.back());
		re.pop_back();
	}
	for(int i=0;i<n;i++){
		cout << ans[i] << " \n"[i==n-1];
	}
}

主播的写法(\(WRONG\)):

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
vector<int> p;
int vis[N];
void init(){
	for(int i=2;i<N;i++){
		if(!vis[i]){
			p.push_back(i);
		}
		for(int v:p){
			if(v*i>=N) break;
			vis[v*i]=1;
			if(i%v==0) break;
		}
	}
}
void solve() {
	int n;
	cin >> n;
	//优先搭配大的质数
	int cnt=lower_bound(p.begin(),p.end(),n+1)-p.begin();
	int mxp=p[cnt-1];
	vector<set<int>> fac(mxp+1);
	vector<int> vis(n+1);
	for(int i=cnt-1;i>=0;i--){
		for(int j=1;j*p[i]<=n;j++){
			if(vis[j*p[i]]) continue;
			fac[p[i]].insert(j*p[i]);
			vis[j*p[i]]=1;
		}
	}
	vector<int> ans(n+1);
	int j=0;
	for(int i=cnt-1;i>=j;i--){
		if(fac[p[i]].empty()) continue;
		if(fac[p[i]].size()==1){
			cout << *fac[p[i]].begin() << " ";
			fac[p[i]].erase(fac[p[i]].begin());
			int tot=2;
			while(fac[p[j]].size()&&tot>0){
//				int v=*fac[p[j]].begin();
				cout << *fac[p[j]].begin() << " ";
				fac[p[j]].erase(fac[p[j]].begin());
				tot--;
			}
			if(tot>0){
				j++;
				while(fac[p[j]].size()&&tot>0){
//					int v=*fac[p[j]].begin();
					cout << *fac[p[j]].begin() << " ";
					fac[p[j]].erase(fac[p[j]].begin());
					tot--;
				}
			}
		}else{
			while(fac[p[i]].size()){
//				int v=*fac[p[i]].begin();
				cout << *fac[p[i]].begin() << " ";
				fac[p[i]].erase(fac[p[i]].begin());
			}
		}
	}
	cout << 1 << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	init();
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}
posted @ 2025-11-28 13:44  alij  阅读(0)  评论(0)    收藏  举报