Codeforces Round 1029 (Div. 3)

Codeforces Round 1029 (Div. 3)

A题

知识点:分类讨论

题意为有\(n\)扇门,每扇门都有一个值\(a_i\),且\(a_i \in \{0,1\}\)\(a_i=0\)时,表示第\(i\)扇门开启,\(a_i=1\)时,表示第\(i\)扇门关闭。现在给一个技能,使用这个技能可以使所有关着的门开启\(x\)秒,穿过一个门需要1秒,如果门关闭的,不能通过。只能通过了第\(i\)扇门,才能到第\(i+1\)扇门,问在最多使用一次技能的情况下,能否通过第\(n\)扇门。

解题:因为第\(i\)扇门是关闭的时候,一定会使用技能,所以从前往后枚举,遇到\(a_i=1\)时,在第\(i\)扇门使用技能,这会保证\([i,i+x-1]\)这段区间的门都是开启状态,所以只需要去判断\([i+x,n]\)这段区间是否有关闭的门即可。时间复杂度为\(O(n)\)

void solve(){
	int n,x;cin >> n >> x;
	vector<int> a(n+1);
	for(int i=1;i<=n;i++){
		cin >> a[i];
	}
	for(int i=1;i<=n;i++){
		if(a[i]==1){
			if(i+x-1<n){
				for(int j=i+x;j<=n;j++){
					if(a[j]==1){
						cout << "No" << endl;
						return;
					}
				}
				cout << "Yes" << endl;
			}else{
				cout << "Yes" << endl;
			}
			break;
		}
	}
}

B题

知识点:构造

题意:

对于一个大小为 \(m\) 的数组 \(a\),收缩操作定义如下:

  • 选择一个索引 \(i\)(满足 \(2 \leq i \leq m-1\)),使得 \(a_i > a_{i-1}\)\(a_i > a_{i+1}\)
  • 从数组中移除元素 \(a_i\)

排列的分数(Score):
给定一个排列 p,其分数定义为对 p 可以执行收缩操作的最大次数。

现在给定排列的长度为\(n\),构造出一个排列,使得这个排列的分数最大。

解题:每次都要选一个元素\(a_i\),使得\(a_i>a_{i-1}且a_i>a_{i+1}\),分析可以看出构造一个这样的排列\(\{1,n,n-1,n-2,\dots,2\}\)的分数为\(n-2\),按照收缩操作定义,一个长度为\(n\)的排列最大分数\(p\leq n-2\),所以这个构造是正确的。时间复杂度为\(O(n)\)

void solve(){
	int n;cin >> n;
	vector<int> a(n+1);
	a[1]=1;
	for(int i=2;i<=n;i++){
		a[i]=n-i+2;
	}
	for(int i=1;i<=n;i++) cout << a[i] << " ";
	cout << endl;
}

C题

知识点:贪心

题意:要把数组\(a\)分成连续的\(k\)个段,要求每一个段都要包含前面一个段所有的元素,这样的一个段称为一个Cool Partition,那么贪心的第一个段的元素肯定越少越好,这样后面一个段更容易包含它,越容易形成Cool Partition。所以我们用map维护每一个段出现过的数字就好,下一个段的最短长度为恰好包含当前段所有元素。比如:

5 8 7 5 8 5 7 8 10 9
第一个段:[1,1] 
第二个段:上一个段出现的元素包括5,所以这个段要从第二个元素开始找到包含上一个段所有元素的最小右端点,即[2,4]。
第三个段:上一个段出现元素包括{5,7,8},所以这个段应该为[5,7]
第4个段:[8,10],但是没有包含{5,7,8},因此这个段不合法

说实话,我感觉这题目有歧义,我认为只要下一个段包含上一个段出现的所有元素,那么这个段和上一个段就形成一个Cool Partition,比如{2,1,1,1,1,1},按照题目意思,输出应该为1,但是我觉得是5呀!!!!。时间复杂度为\(O(n)\)

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,mp1;
	int ans=1;
	mp[a[1]]++;
	for(int i=2;i<=n;i++){
		mp1[a[i]]++;
		if(mp[a[i]]){
			mp.erase(a[i]);
			if(mp.size()==0){
				ans++;
				mp=mp1;
				mp1.clear();
			}
            //注意这个细节
		}else mp.erase(a[i]);
	}
	cout << ans << endl;
}

D题

知识点:思维

题意:

优素福想要让一个数组 $ a_1, a_2, \ldots, a_n $ 爆炸。当数组的所有元素都变为零时,数组就会爆炸。

在一次操作中,优素福可以精确地执行以下操作之一:

  1. 对于数组中的每个索引 $ i $,将 $ a_i $ 减去 $ i $。
  2. 对于数组中的每个索引 $ i $,将 $ a_i $ 减去 $ n - i + 1 $。

你的任务是帮助优素福确定是否可以通过任意次数的操作让数组爆炸。

输入
输入的第一行包含一个整数 $ t $(\(1 \leq t \leq 10^4\))——测试用例的数量。

每个测试用例的第一行包含一个整数 \(n\)\(2 \leq n \leq 2 \cdot 10^5\))——数组的大小。

每个测试用例的第二行包含 $ n $ 个整数 \(a_1, a_2, \ldots, a_n\)\(1 \leq a_i \leq 10^9\))——数组的元素。

保证所有测试用例中 \(n\) 的总和不超过 \(2 \cdot 10^5\)

输出
对于每个测试用例,如果优素福可以让数组爆炸,输出 "YES",否则输出 "NO"。

你可以用任意大小写形式输出答案。例如,字符串 "yEs"、"yes"、"Yes" 和 "YES" 都会被视为有效的肯定回复。

解题:可以逆向的从\([0,0,0,0,0,\dots,0]\)去使用操作1和2的逆操作看能不能推回给定的数组。我们假设将数组\(a\)变为全0使用了\(x\)次操作1,\(y\)次操作2,那么对于数组\(a\)的每一个元素的操作次数都为\(x\)次操作1和\(y\)次操作2,那么有\(a_i=x\times i+y\times (n-i+1)\),我们只需要随便拿两个数\(a_i和a_j\),求出\(x\)\(y\),然后去枚举其他元素是否合法,即满足前面那个等式。当然需要去讨论一下\(x\)\(y\)之间的关系。如果求出来\(x\)\(y\)都是非负整数,且所有元素都满足,那么就输出"Yes",否则输出"No"

时间复杂度为\(O(n)\)

bool situ1(){
	if(a[1]/(n+1)*(n+1)==a[1]){
		int x=a[1]/(n+1);
		for(int i=2;i<=n;i++){
			if(a[i]!=x*(n+1)){
				return false;
			}
		}
	}else{
		return false;
	}
	return true;
}

bool situ2(){
	//由第一个元素和最后一个元素确定x和y
	bool f=true;
	if((a[n]-a[1])/(n-1)*(n-1)==(a[n]-a[1])){
		int t=(a[n]-a[1])/(n-1),x=0,y=0;
		if(a[1]>=t&&(a[1]-t)/(n+1)*(n+1)==(a[1]-t)){
			y=(a[1]-t)/(n+1);
			x=t+y;
			for(int i=2;i<n;i++){
				if(a[i]!=y*(n+1)+t*i){
					f=false;
					break;
				}
			}
		}else{
			return false;
		}
	}else{
		return false;
	}
	return f;
}

void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
	}
	//三种情况
	if(situ1()){
		cout << "Yes" << endl;
		return;
	}
	if(a[1]<a[n]){
		if(situ2()){
			cout << "Yes" << endl;
			return;
		}
	}else{
		for(int i=1;i<=n/2;i++){
			swap(a[i],a[n-i+1]);
		}
		if(situ2()){
			cout << "Yes" << endl;
			return;
		}
	}
	cout << "No" << endl;
}

E题

知识点:思维

题意:

给你两个长度均为n的整数数组a和b。

你可以任意次数执行以下操作:

  • 选择一个索引\(i\)\(1 ≤ i ≤ n - 1\)),将\(a_i\)设为\(b_{i+1}\) ,或者将\(b_i\)设为\(a_{i+1}\)

在执行任何操作之前,你可以选择一个索引\(i\)\(1 ≤ i ≤ n - 1\)),从两个数组中同时移除\(a_i\)\(b_i\)。这种移除操作最多只能进行一次。

对于两个长度为m的数组c和d,它们的“匹配数”指的是满足cⱼ = dⱼ 的位置j(1 ≤ j ≤ m)的数量。

你的任务是计算通过操作能达到的最大匹配数。

输入
输入的第一行包含一个整数t(1 ≤ t ≤ 10⁴)——测试用例的数量。每个测试用例的描述如下。

每个测试用例的第一行包含一个整数n(2 ≤ n ≤ 2×10⁵)——数组a和b的长度。

第二行包含n个整数a₁, a₂, …, aₙ(1 ≤ aᵢ ≤ n)——数组a的元素。

第三行包含n个整数b₁, b₂, …, bₙ(1 ≤ bᵢ ≤ n)——数组b的元素。

保证所有测试用例中n的总和不超过2×10⁵。

输出
对于每个测试用例,输出一个整数——该测试用例的答案。

解题:分析一下我们发现,我们的目标是通过操作构造\(a_i==b_i\),那么对于每一个\(a_i\),我们要使得\(b_i\)能够等于\(a_i\),那么\(b_i\)可以去看后面的\(\{a_{i+1},b_{i+2},a_{i+3},b_{i+4},\dots,a_{i+2k+1},b_{i+2k+2}\},其中k=0,1,2,3,\dots\),也就是像个w形,但是有了一次移除操作后,你会发现现在从\(i+1\)\(n\)的所有\(a\)数组和\(b\)数组中的元素除了\(b_{i+1}\)都可以通过操作赋值给\(b_i\),同理对于每一个\(b_i\),从\(i+1\)\(n\)的所有\(a\)数组和\(b\)数组中的元素除了\(a_{i+1}\)都可以通过操作赋值给\(a_i\),所以我们从后往前枚举,同时用set维护两个集合,同时统计答案。

时间复杂度为\(O(n\times \log n)\)

int a[maxn],b[maxn],n;
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
	}
	for(int i=1;i<=n;i++){
		cin >> b[i];
	}
	if(a[n]==b[n]){
		cout << n << endl;
		return;
	}
	int ans=0;
	set<int> se1,se2;//se1维护包括左上的元素,se2维护包括左下的元素
	for(int i=n-1;i>=1;i--){
		//对于a找se1,b找se2
		if(!se1.count(a[i+1])) se1.insert(a[i+1]);
		if(!se2.count(b[i+1])) se2.insert(b[i+1]);
		if(se1.count(a[i])) ans=max(ans,i);
		if(se2.count(b[i])) ans=max(ans,i);
		if(a[i]==b[i]) ans=max(ans,i);
		if(!se1.count(b[i+1])) se1.insert(b[i+1]);
		if(!se2.count(a[i+1])) se2.insert(a[i+1]);
	}
	cout << ans << endl;
}

F题

知识点:dfs,分类讨论,快速幂

题意就不说啦,可以去看:[https://codeforces.com/contest/2117/problem/F]

解题:从题目分析可以知道这棵树最多只能有两个叶子节点,那么分别考虑一个叶子节点也就是一条链,和两个叶子节点,再去手动模拟一下就可以得到计算的公式了。只是注意一些特殊情况就好。

时间复杂度为\(O(n)\)代码写的不是太好

const int maxn=2e5+9,mod=1e9+7;
vector<int> tr[maxn];
int n,sz[maxn],last,dfn[maxn],tot;
int ksm(int base,int p){
	int res=1;
	while(p){
		if(p&1) res=res*base%mod;
		base=base*base%mod;
		p>>=1;
	}
	return res;
}
//从idx去走到根节点
void dfs(int u,int p){
	dfn[u]=++tot;
	for(int v:tr[u]){
		if(v==p) continue;
		dfs(v,u);
	}
}

int get(int u,int p){
	if(tr[u].size()==1) return 1;
	int res=1;
	for(int v:tr[u]){
		if(v==p) continue;
		res+=get(v,u);
	}
	return res;
}

void solve(){
	cin >> n;
	tot=0;
	for(int i=1;i<=last;i++){
		sz[i]=0;
		tr[i].clear();
		dfn[i]=0;
	}
	for(int i=1;i<n;i++){
		int u,v;cin >> u >> v;
		tr[u].push_back(v);
		tr[v].push_back(u);
	}
	dfs(1,0);
	int cnt=0,idx=0;//cnt记录叶子节点个数,idx记录有两个孩子的点的下标
	for(int i=1;i<=n;i++){
		if(tr[i].size()==1) cnt++;
		if(tr[i].size()==3) idx=i;
	}
	if(tr[1].size()==1) cnt--;
	if(cnt>2){
		cout << 0 << endl;
	}else if(cnt==1){
		cout << ksm(2,n) << endl;
	}else if(cnt==2){
		int ans=0;
		//特判根为idx
		if(tr[1].size()==2){
			int len1=get(tr[1][0],1);
			int len2=get(tr[1][1],1);
			if(len1==len2){
				ans=4;
			}else{
				int len=abs(len1-len2);
				ans=(ksm(2,len+1)+ksm(2,len))%mod;
			}
		}else{
			//找到了idx
			//idx的父节点
			int p=0,d=n+1;
			for(int v:tr[idx]){
				if(d>dfn[v]){
					p=v;
					d=dfn[v];
				}
			}
			vector<int> len;
			for(int v:tr[idx]){
				if(v==p) continue;
				int l=get(v,idx);
				len.push_back(l);
			}
			int diff=abs(len[1]-len[0]);
			if(diff==0){
				ans=ksm(2,dfn[idx]+1);
			}else{
				ans=(ksm(2,diff+dfn[idx])+ksm(2,diff+dfn[idx]-1))%mod;
			}
		}
		cout << ans << endl;
	}
	last=n;
}
posted @ 2025-06-10 13:23  alij  阅读(97)  评论(0)    收藏  举报