2025蓝桥杯C++A组省赛 题解

news2025/4/15 19:51:31

昨天打完蓝桥杯本来想写个 p y t h o n python python A A A 组的题解,结果被队友截胡了。今天上课把 C + + A C++A C++A 组的题看了,感觉挺简单的,所以来水一篇题解。

这场 B B B 是一个爆搜, C C C 利用取余的性质比较好写, D D D 是个猜猜题,当然如果模拟也是可以的, E G EG EG 是数学, F H FH FH 需要预处理。 H H H 是基环树,需要拓扑排序+单调队列维护区间最大值。

p y t h o n python python c + + c++ c++ 相比,我觉得 c + + c++ c++ 组的题要更简单(可能也有我 p y t h o n python python 不太熟悉的原因)。 p y t h o n python python 组全是暴力,我还暴力苦手,最后三题分别是爆搜剪枝,单调栈优化+并查集,反悔贪心。 c + + c++ c++ 组只有 B B B 是暴力,大部分是偏思维和算法,打多了 a c m acm acm 当然会觉得简单一些。

因为没有 O J OJ OJ,所以我也不能保证思路和代码一定正确,仅当作参考,欢迎斧正。

Python A组题解

注:A-H在洛谷测试全部通过,但是洛谷民间数据较弱,不排除会有bug


A 寻找质数

题面:

2025 2025 2025 个质数是多少?

思路:

直接从小到大暴力枚举每一个数,判断是否是素数,到第 2025 2025 2025 个停止就可以了。

n n n 个数里大概有 n ln ⁡ n \dfrac{n}{\ln n} lnnn 个数是素数, ln ⁡ n \ln n lnn 大概也就是十几,所以不需要担心越界问题。

code:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

vector<int> primes;
bool check(int n){
	if(n==1)return false;
	for(int i=2;i*i<=n;i++)
		if(n%i==0)
			return false;
	return true;
}

int main(){
	for(int i=2;primes.size()<2025;i++)
		if(check(i))
			primes.push_back(i);
	cout<<primes.back();
	return 0;
} 

B 黑白棋

题面:

请添加图片描述

给你一个 6 × 6 6\times 6 6×6 的棋盘,有的位置一开始就填有棋子,其余格子需要你进行填充,要求:

  1. 每一行,每一列均只有三个白棋,三个黑棋
  2. 在某一行或某一列中,不能出现三个同色棋子连续的情况
  3. 每一行棋子排列方式唯一,与其他行各不相同。同理每一列。行列之间不受限制。

题目保证有唯一解。按照以下格式输出答案:

黑色棋子用 1 1 1 表示,白色棋子用 0 0 0 表示。
从左到右、从上到下的顺序,依次遍历棋盘上的所有格子,并将这些值拼接成一个长度为 36 36 36 的字符串。

思路1:

暴力枚举所有状态,然后判断状态是否成立。

枚举除了已经确定的位置以外的位置的落子情况(总共有 26 26 26 个位置),每个位置只有两种情况(放白棋或者放黑棋),所以总的可能数是 2 26 ≈ 6 × 1 0 8 2^{26}\approx 6\times 10^8 2266×108

枚举到一种状态后,将已经确定的位置的状态加入枚举的状态中。然后判断状态是否成立即可。

总的时间复杂度大概是 总可能数 × \times × (加入已经确定的位置的状态 + + + 判断),后面这个东西的时间复杂度是一个大常数,预估大概也就是 100 100 100。所以能跑动,就是慢一点。

code:

跑了 139 139 139

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
typedef long long ll;

void print(string s){
	for(int i=0;i<6;i++,puts(""))
		for(int j=0;j<6;j++)
			cout<<s[i*6+j]<<" ";
}

string b2s(ll n){//二进制转string 
	string s;
	for(ll i=0;i<36;i++)
		s+="01"[(n>>i)&1];
	return s;
}
int s2b(string s){//string转二进制
	ll n=0;
	for(auto x:s)
		n=(n<<1)+(x-'0');
	return n;
}
int idx[]={0,1,3,9,16,17,26,29,31,34};
int colors[]={1,0,0,0,0,0,1,1,0,1};//1,1,1,1,1,1,1,1,1,1
string treat(string s){
	s=s.substr(0,26);//因为在加入10个固定位置的状态前只有26位 
	for(int i=0;i<10;i++){
		int id=idx[i];
		int c=colors[i];
		//向id上插入c
		s=s.substr(0,id)+(c?"1":"0")+s.substr(id);
	}
	return s;
}

bool check(string s){
	for(int i=0;i<6;i++){
		int t=0;
		for(int j=0;j<6;j++)
			if(s[i*6+j]=='0')
				t++;
		if(t!=3)return false;
	}
	for(int j=0;j<6;j++){
		int t=0;
		for(int i=0;i<6;i++)
			if(s[i*6+j]=='0')
				t++;
		if(t!=3)return false;
	}
	
	for(int i=0;i<6;i++)
		for(int j=0;j+2<6;j++)
			if(s[i*6+j]==s[i*6+j+1] && s[i*6+j+1]==s[i*6+j+2])
				return false;
	for(int j=0;j<6;j++)
		for(int i=0;i+2<6;i++)
			if(s[i*6+j]==s[(i+1)*6+j] && s[(i+1)*6+j]==s[(i+2)*6+j])
				return false;
	
	set<string> S;
	for(int i=0;i<6;i++){
		string t;
		for(int j=0;j<6;j++)
			t+=s[i*6+j];
		S.insert(t);
	}
	if(S.size()!=6)return false;
	S.clear();
	for(int j=0;j<6;j++){
		string t;
		for(int i=0;i<6;i++)
			t+=s[i*6+j];
		S.insert(t);
	}
	if(S.size()!=6)return false;
	return true;
}

int main(){
	for(ll i=0;i<(1<<26);i++){
		string t=treat(b2s(i));
		if(check(t)){
			//print(t);
			cout<<t;//101001010011101100010110011001100110
			break;
		}
	}
	return 0;
} 
/*
1 0 1 0 0 1
0 1 0 0 1 1
1 0 1 1 0 0
0 1 0 1 1 0
0 1 1 0 0 1
1 0 0 1 1 0

*/

思路2:

爆搜剪枝

按顺序枚举每个点,尝试落子。然后加点剪枝。

这样搜的好处是可以剪枝,不像思路1需要枚举到所有的状态,因为这个题的要求条件非常苛刻,所以可以狠狠剪枝,所以速度要快很多。

思路3:

直接手玩

首先很容易发现 ( 1 , 3 ) (1,3) (1,3) ( 3 , 4 ) (3,4) (3,4) 只能放黑棋,否则会导致三个白棋行/列连续。

请添加图片描述
接下来假设 ( 4 , 4 ) (4,4) (4,4) 放白棋,那么可以推出以下局面:

  1. 因为第四列已经有三白棋,因此 ( 5 , 4 ) (5,4) (5,4) ( 6 , 4 ) (6,4) (6,4) 均为黑棋
  2. 因为第五行已经有三黑棋,因此 ( 5 , 1 ) (5,1) (5,1) ( 5 , 2 ) (5,2) (5,2) ( 5 , 5 ) (5,5) (5,5) 均为白棋
  3. 因为第二列已经有三白棋,因此 ( 2 , 2 ) (2,2) (2,2) ( 3 , 2 ) (3,2) (3,2) ( 4 , 2 ) (4,2) (4,2) 均为黑棋
  4. 连续三黑棋,矛盾

在这里插入图片描述
因此 ( 4 , 4 ) (4,4) (4,4) 放黑棋请添加图片描述


C 抽奖

题意:

抽奖机有三个转轮,每个转轮上有 n n n 个数字图案,标号为 1 ∼ n 1 \sim n 1n ,按照从 1 ∼ n 1\sim n 1n 顺序转动,当转到第 n n n 个图案时会从第一个继续开始。奖项如下:

  1. 三个相同的图案,积分 + 200 +200 +200
  2. 两个相同的图案,积分 + 100 +100 +100
  3. 三个数字图案,从左到右连续(例如 123 123 123 ),积分 + 200 +200 +200
  4. 三个数字图案,经过排序后连续,(例如 213 , 321 213, 321 213,321),积分 + 100 +100 +100

抽奖机处于初始状态,三个转轮都处于第一个位置。每次开始抽奖,都会产生三个对应的随机数 x i 1 , x i 2 , x i 3 x_{i1}, x_{i2}, x_{i3} xi1,xi2,xi3 ,表示第 j j j 个转轮会向后转动 x i j x_{i j} xij 次停下。下次抽奖时,转轮会从上一次转动后的位置开始继续转动。

注意,一次抽奖最多只能获得一次积分,如果同时命中多个奖项,以积分最大的那个奖项为准。

请问,如果执行 m m m 次抽奖,总积分值是多少?

n , m ≤ 1 0 3 n,m\le 10^3 n,m103 a i , b i , c i ≤ 9 a_i,b_i,c_i\le 9 ai,bi,ci9 x i j ≤ 1000 x_{ij}\le 1000 xij1000

思路:

把循环和取余联系起来,而不是一个一个数,就会好写很多。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e3+5;

int n,m;
int a[3][maxn];
int p[3];

int main(){
	cin>>n;
	for(int i=0;i<3;i++)
		for(int j=0;j<n;j++)
			cin>>a[i][j];
	cin>>m;
	int ans=0;
	for(int i=1,t;i<=m;i++){
		for(int i=0;i<3;i++){
			cin>>t;
			p[i]=(p[i]+t)%n;
		}
		
		int x[]={a[0][p[0]],a[1][p[1]],a[2][p[2]]};
		if(x[0]==x[1] && x[1]==x[2])ans+=200;
		else if(x[0]+1==x[1] && x[1]+1==x[2])ans+=200;
		else {
			if(x[0]>x[1])swap(x[0],x[1]);
			if(x[1]>x[2])swap(x[1],x[2]);
			if(x[0]>x[1])swap(x[0],x[1]);
			
			if(x[0]==x[1] || x[1]==x[2])ans+=100;
			if(x[0]+1==x[1] && x[1]+1==x[2])ans+=100;
		}
	}
	cout<<ans<<endl;
	return 0;
}
/*
4
3 2 4 1
2 2 2 2
4 3 0 9
3
4 4 4
3 1 1
40 39 2

*/

D 红黑树

题意:

小蓝最近学习了红黑树,红黑树是一种特殊的二叉树,树上的结点有两种类型:红色结点和黑色结点。
小蓝在脑海中构造出一棵红黑树,构造方式如下:

  1. 根结点是一个红色结点
  2. 如果当前结点 c u r N o d e curNode curNode 是红色结点,那么左子结点 c u r N o d e . l e f t curNode.left curNode.left 是红色结点,右子结点 c u r N o d e . r i g h t curNode.right curNode.right 是黑色结点
  3. 如果当前结点 c u r N o d e curNode curNode 是黑色结点,那么左子结点 c u r N o d e . l e f t curNode.left curNode.left 是黑色结点,右子结点 c u r N o d e . r i g h t curNode.right curNode.right 是红色结点

请添加图片描述

输入格式:

输入的第一行包含一个正整数 m m m ,表示小蓝挑选的结点数。
接下来 m m m 行,每行包含两个正整数 n i   k i n_i\ k_i ni ki ,用一个空格分隔,表示小蓝挑选的结点是第 n i n_i ni 行(从上往下数)第 k i k_i ki 个(从左往右数)结点。

输出格式:

输出 m m m 行,每行包含一个字符串,依次表示小蓝每次挑选的结点的答案。RED 表示红色结点,BLACK 表示黑色结点。

数据范围:

m ≤ 10 m\le 10 m10 n i ≤ 30 n_i\le 30 ni30 k i ≤ 2 n i − 1 k_i\le 2^{n_i-1} ki2ni1

思路:

其实仔细想想也不算神秘猜猜题。毕竟这是正了八经的完全二叉树的性质。

对一个完全二叉树,从上到下,从左到右进行编号,会有一个性质:对一个节点,编号为 i i i,那么它的左孩子节点编号为 2 i 2i 2i,右孩子节点的编号为 2 i + 1 2i+1 2i+1
在这里插入图片描述
观察一下,你很容易看出来对一个节点,它的左孩子是保持它的颜色,它的右孩子是翻转它的颜色。如果把编号看成二进制数,左孩子编号相当于在它的编号后面加一个 0 0 0,右孩子编号相当于在它的编号后面加一个 1 1 1。这说明了什么?这说明节点颜色和它编号二进制的 1 1 1 的个数有关。

在这里插入图片描述

所以你只需要算出第 n n n 行第 k k k 个节点的编号,然后数一下它的二进制中 1 1 1 的个数即可。

再观察一下可以发现第 n n n 行的点只是二进制位上第 n − 1 n-1 n1 位(从第零位开始算)为 1 1 1,除去最高位 1 1 1,剩余的二进制位组成的数就是 k − 1 k-1 k1

code:

#include <iostream>
#include <cstdio>
using namespace std;

int count(int x){
	int cnt=0;
	while(x){
		if(x&1)cnt++;
		x>>=1;
	}
	return cnt;
}

int n;

int main(){
	cin>>n;
	for(int i=1,_,k;i<=n;i++){
		cin>>_>>k;
//		cout<<k<<" "<<count(k-1)<<endl;
		cout<<((count(k-1)%2)?"BLACK":"RED")<<endl;
	}
	return 0;
}
/*
2
1 1
2 2

*/

E 黑客

题意:

小蓝正在两台电脑之间拷贝数据,数据是一个 n × m n\times m n×m 大小的正整数矩阵,因此总共有 n × m + 2 n\times m+2 n×m+2 个由空格分开的整数,其中前两个整数分别为 n n n m m m。然而,有黑客入侵了小蓝的电脑,导致这 n × m + 2 n\times m+2 n×m+2 个正整数的顺序被打乱了,小蓝想知道最多可能有多少个不同的原矩阵。

两个矩阵相同当且仅当它们行数相同、列数分别相同,且每个位置上的数相同。

输入格式:

输入的第一行包含一个正整数 n × m + 2 n\times m+2 n×m+2

第二行包含 n × m + 2 n\times m+2 n×m+2 个正整数 a 1 , a 2 … a n × m + 2 a_1,a_2\dots a_{n\times m+2} a1,a2an×m+2 ,相邻整数之间使用一个空格分隔。

输出格式:

输出一行包含一个整数表示答案。答案可能很大,请输出答案除以 1000000007 的余数。

数据范围:

n × m + 2 ≤ 5 × 1 0 5 n\times m +2\le 5\times 10^5 n×m+25×105 a i ≤ 5 × 1 0 5 a_i\le 5\times 10^5 ai5×105

思路:

首先需要取出两个数作为 n , m n,m n,m。其次这个两个数肯定是 n × m n\times m n×m 的因数(废话)。所以我们可以 O ( n ) O(\sqrt n) O(n ) 枚举总数的因数,(如果存在这两个数)去除这两个数,并计算剩余 n × m n\times m n×m 个数的答案。

因为矩阵相同只需要对应位置上的数相同,这个数原本的位置不重要,所以我们可以把所有数按照值统计起来。

如果原本的数各不相同,那么显然总的可能数就是 n m nm nm 个数的全排列,即 A n m n m = ( n m ) ! A_{nm}^{nm}=(nm)! Anmnm=(nm)!。如果其中有 k k k 个数是相等的,我们就需要消除它们之间全排对整体全排列的影响。也就是除序原理。它们 k k k 个数全排列有 k ! k! k! 种可能,这意味着在整体的全排列中会有 k ! k! k! 个其实是一种排列(因为整体在全排时把它们看作是不同的元素),所以需要在原本的基础上除以 k ! k! k!。以此类推,对多个不同的个数大于 1 1 1 的数也都是这样。

假设值为 i i i 的数有 c n t i cnt_i cnti 个,那么答案就是: ( n m ) ! ∏ i c n t i ! \dfrac{(nm)!}{\prod_icnt_i!} icnti!(nm)!

所以再预处理一下阶乘和阶乘倒数即可。

因为 n , m n,m n,m m , n m,n m,n 算两种情况,所以在枚举因数的时候,如果因数 p p p n m / p nm/p nm/p 不一致,答案需要计两遍。

算阶乘可以用 n ! = ( n − 1 ) ! ∗ n n! = (n-1)!*n n!=(n1)!n 递推,算阶乘倒数可以用 1 ( n − 1 ) ! = 1 n ! ∗ n \dfrac{1}{(n-1)!}=\dfrac{1}{n!}*n (n1)!1=n!1n 倒推。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=5e5+5;
const int N=5e5;
typedef long long ll;
const ll mod=1e9+7;

ll qpow(ll a,ll b){//a^b (%mod)
	ll ans=1,base=a;
	while(b){
		if(b&1)ans=ans*base%mod;
		base=base*base%mod;
		b>>=1;
	}
	return ans;
}
ll inv(ll x){
	return qpow(x,mod-2);
}

ll nm;
ll cnt[maxn];
ll fac[maxn],ifac[maxn];

int main(){
	fac[0]=1;
	for(int i=1;i<=N;i++)fac[i]=fac[i-1]*i%mod;
	ifac[N]=inv(fac[N]);
	for(int i=N;i>=1;i--)ifac[i-1]=ifac[i]*i%mod;
	
	ll ans=0;
	cin>>nm;
	for(int i=1,t;i<=nm;i++){
		cin>>t;
		cnt[t]++;
	}
	nm-=2;
	
	for(ll i=1;i*i<=nm;i++){
		if(nm%i!=0)continue;
		cnt[i]--;
		cnt[nm/i]--;
		if(cnt[i]>=0 && cnt[nm/i]>=0){
//			cout<<i<<" "<<nm/i<<endl; 
			ll t=fac[nm];
			for(int j=1;j<=N;j++)
				t=t*ifac[cnt[j]]%mod;
//			cout<<t<<endl;
			ans+=t;
			if(i*i!=nm)ans+=t;
			ans%=mod;
		}
		cnt[i]++;
		cnt[nm/i]++;
	}
	cout<<ans;
	return 0;
}
/*
6
2 2 1 4 3 3

*/

F 好串的数目

题意:

对于一个长度为n 的字符串 s = s 0 s 1 … s n − 1 s=s_0s_1\dots s_{n-1} s=s0s1sn1 来说,子串的定义是从中选出
两个下标 l , r l,r l,r 0 ≤ l ≤ r ≤ n − 1 0\le l\le r\le n-1 0lrn1),这之间所有的字符组合起来的一个新的字符串:
s ’ = s l s l + 1 … s r s’ = s_ls_{l+1} \dots s_r s=slsl+1sr 就是其中一个子串。

现在给出一个只有数字字符 0 ∼ 9 0\sim9 09 组成的数字字符串,小蓝想要知道在其
所有的子串中,有多少个子串是好串。一个子串是好串,当且仅当它满足以下
两个条件之一:

  1. 单字符子串一定是好串,即当子串长度为 1 1 1 时,它总是好串;
  2. 长度大于 1 1 1 时,可以拆分为两个连续非递减子串。

其中,一个串 p = p 0 p 1 … p k − 1 p = p_0p_1\dots p_{k-1} p=p0p1pk1 为连续非递减子串是指,对于所有 1 ≤ i < k 1 \le i < k 1i<k,满足 p i = p i − 1 p_i=p_{i-1} pi=pi1 p i = p i + 1 + 1 p_i = p_{i+1} + 1 pi=pi+1+1 。即数字串中的每一个数字,要么等于上一个数字,要么等于上一个数字加 1 1 1 。例如 12233 、 456 12233 、456 12233456 是连续非递减子串。

输入格式:

输入一行包含一个字符串 s s s

输出格式:

输出一行包含一个整数表示答案,即好串的数目。

数据范围:

1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105 s s s 中只包含数字字符 0 ∼ 9 0\sim 9 09

思路:

如果我们把整个串划分成若干段连续非递减子串,比如 122 ‾ 5 ‾ 8 ‾ \underline{122}\underline{5}\underline{8} 12258。只要我们选取的区间不包含超过两个段,那么它就是一个好串。

我们可以枚举左端点,然后快速处理出右端点。其实也就是对一个左端点,我们需要知道左端点所在段的后一个段的右端点在哪里。我们可以预处理这个东西,于是就做完了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;

int n;
string s;
int mir[maxn];

int main(){
	cin>>s;
	n=s.length();
	s=" "+s;
	
	for(int r=n,l=n+1,lst=n+1;r>=1;r=l-1){//双指针拓展段,每次循环得到一段,段为[l,r]
		l--;
		while(l-1>=1 && (s[l]-s[l-1]==1 || s[l]-s[l-1]==0))l--;
		for(int i=l;i<=r;i++)mir[i]=lst;
//		cout<<l<<" "<<r<<" "<<lst<<endl;
		lst=r+1;
	}
	
	ll ans=0;
	for(int l=1,r,len;l<=n;l++){//右端点在[l,r)任取 
		r=mir[l];
		len=r-l;
		ans+=len;
	}
	cout<<ans;
	return 0;
}
/*
12258

97856

*/

G 地雷阵

题意:

小蓝正在平面直角坐标系中的第一象限里玩一个逃生小游戏,在第一象限中埋有 n n n 颗地雷,第 i i i 颗地雷的坐标为 ( x i , y i ) (x_i, y_i) (xi,yi) ,触发范围为以 ( x i , y i ) (x_i, y_i) (xi,yi) 为圆心,半径为 r i r_i ri 的圆。一旦小蓝走进了圆内就会触发地雷导致游戏失败。小蓝初始在原点 ( 0 , 0 ) (0, 0) (0,0) 上,他需要在第一象限内选择一个方向一直往前走,如果能不触发任何地雷即可成功通关游戏。他想知道在 [ 0 , π 2 ] [0,\dfrac{\pi}{2}] [0,2π] 中均匀随机选择一个方向,即在 0 ° 0\degree (朝向 x x x 轴正方向)至 90 ° 90\degree 90°(朝向 y y y 轴正方向)之间随机选择一个方向,通关游戏的概率是多少?

输入格式:

输入的第一行包含一个正整数 n n n

接下来 n n n 行,每行包含三个正整数 x i , y i , r i x_i, y_i, r_i xi,yi,ri,相邻整数之间使用一个空格分隔。

输出格式:

输出一行包含一个实数,四舍五入保留三位小数,表示答案。

数据范围:

n ≤ 1 0 5 n\le 10^5 n105 x i , x y ≤ 1 0 4 x_i,x_y\le 10^4 xi,xy104 r i ≤ m i n ( x i , y i ) r_i\le min(x_i,y_i) rimin(xi,yi)

思路:

r i ≤ m i n ( x i , y i ) r_i\le min(x_i,y_i) rimin(xi,yi) 说明所有圆不会超出第一象限,可以少一些特殊情况判断。

如果在第一象限有一个圆,它的圆心坐标为 ( x , y ) (x,y) (x,y),半径为 r r r。那么可以得到下图:
请添加图片描述
不难发现在 [ 0 , π 2 ] [0,\dfrac{\pi}{2}] [0,2π] 中, [ α − θ , α + θ ] [\alpha-\theta,\alpha+\theta] [αθ,α+θ] 这段是不能取的,而 α = arctan ⁡ y x , θ = arcsin ⁡ r R = arcsin ⁡ r x 2 + y 2 \alpha=\arctan{\dfrac yx},\theta=\arcsin{\dfrac{r}{R}}=\arcsin{\dfrac{r}{\sqrt{x^2+y^2}}} α=arctanxy,θ=arcsinRr=arcsinx2+y2 r,其中 R R R 代表从坐标原点到圆心的距离。

n n n 个圆,那么就有 n n n 段不能取的区间。我们取这些区间的并集,假设长度为 l e n len len,那么总长度为 π 2 \dfrac{\pi}{2} 2π,不通关的概率就是 l e n π 2 \dfrac{len}{\dfrac{\pi}{2}} 2πlen,通关概率就是 1 − l e n π 2 1-\dfrac{len}{\dfrac{\pi}{2}} 12πlen

code:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
const double pi=acos(-1);
#define pdd pair<double,double> 

int n;
vector<pdd> range;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		double x,y,r;
		cin>>x>>y>>r;
		double alpha=atan(y/x);
		double theta=asin(r/sqrt(x*x+y*y));
		range.push_back(pdd(alpha-theta,alpha+theta));
	}
	sort(range.begin(),range.end());
	double p=0,len=0;
	for(auto [l,r]:range){
		if(r<p){
			continue;
		}
		else if(l<p){
			len+=r-p;
			p=r;
		}
		else {
			len+=r-l;
			p=r;
		}
	}
	printf("%.3f",1-len/(pi/2));
	return 0;
}
/*
1
2 2 1

2
1 3 1
3 1 1

*/

H 扫地机器人

题意:

在一个含有 n n n 个点 n n n 条边的无重边无自环的连通无向图中,有一个扫地机器人在执行清扫作业,其中结点 i i i 的标记 t i ∈ { 0 , 1 } t_i \in \{0,1\} ti{0,1} 如果为 1 1 1 ,则说明该结点需要进行清扫,扫地机器人在到达这个结点时会顺便进行清扫工作。机器人想知道,如果选定任意结点出发,每条边只能经过一次的话,最多能清扫多少个待清扫结点?

输入格式:

输入的第一行包含一个正整数 n n n

第二行包含 n n n 个整数 t 1 , t 2 , … , t n t_1,t_2,\dots,t_n t1,t2,,tn,相邻整数之间使用一个空格分隔。

接下来 n n n 行,每行包含两个正整数 u i , v i u_i, v_i ui,vi ,用一个空格分隔,表示结点 u i u_i ui 和结点 v i v_i vi 之间有一条边。

输出格式:

输出一行包含一个整数表示答案。

数据范围:

n ≤ 5 × 1 0 5 n\le 5\times 10^5 n5×105 t i ∈ { 0 , 1 } t_i \in \{0,1\} ti{0,1} 1 ≤ u i , v i ≤ n 1\le u_i,v_i\le n 1ui,vin

思路:

终于是通过洛谷的数据了,这题阴的很,出题人坏的很。重构一遍发现该错的地方还是错,不是写法有问题,而是情况没讨论全。

n n n 个点 n n n 条边,还无重边无自环,连通图。这就是一个基环树。

基环树说白了就是在一棵树的基础上加一条边,于是变成了一种图。它存在唯一的环,环上的每个点都可以作为树根向外衍生出一棵树。因为无重边无自环,所以环长最小为 3 3 3

比如这个题的样例对应的图(带圈的表示待清扫结点):
请添加图片描述
我们的答案路径有三种情况,这就是这个题阴的地方,我们需要对每种情况进行计数,并取最大值。三种情况分别为:

  1. 在以环上某点为根的一颗树内。比如上图的 8 → 2 → 9 8\rightarrow 2\rightarrow 9 829
  2. 从某棵树的树叶开始,走到树根(树根在环上),然后走到另一个树根,再走到这棵树的树叶。比如这个样例的答案路径就是 3 → 1 → 4 → 6 → 7 3\rightarrow 1\rightarrow 4\rightarrow 6\rightarrow 7 31467
  3. 环上某点有两个以上的儿子,以其中一个儿子所在子树开始,走到环上该点,绕环转一圈回到该点,再进入另一个儿子所在子树。

在这里插入图片描述

因为题目只要求边只能走一遍,但是没说点不能走多遍。阴不阴?


情况一和情况三都比较好处理,主要看你的实现方式。假设我们把待清理点叫做 1 1 1 点。

  1. 如果你用拓扑排序找环:那么找环的过程中,相当于对每棵树从叶向上遍历,那么遍历过程中顺便把某点的两个最大子树的 1 1 1 点个数加起来,就得到了情况一的答案。然后枚举环上各点,将它的两个最大子树和环上的 1 1 1 点个数加起来就是情况三的答案
  2. 如果你用 d f s dfs dfs 找环(这题明确就一个环,也不需要找连通分量,所以没必要上 t a r j a n tarjan tarjan,一个 d f s dfs dfs 就解决了):找到环后,以每个点为根,单独进行 d f s dfs dfs,直接去算情况一和情况三的答案。

对于情况二,如果用拓扑排序,我们可以处理出以环上某点作为起点,向环外走可达的最大 1 1 1 点个数。于是我们就可以用拓扑排序来删点,边删边算上面说的东西。最后剩一下一个环。

我们可以把这个环展开,展开后有 t n tn tn 个点,按顺序重新编号,假设 a i a_i ai 表示第 i i i 个点是或不是 1 1 1 点, b i b_i bi 表示第 i i i 个点向环外走可达的最大 1 1 1 点个数(也就是上面提到的那个东西),从某个点到另外一个点的答案就是 b i + a i + 1 + a i + 2 + ⋯ + a j − 1 + b j b_i+a_{i+1}+a_{i+2}+\dots+a_{j-1}+b_j bi+ai+1+ai+2++aj1+bj

我们要算答案,就是要枚举左端点 i i i,然后枚举右端点 j j j,计算上面的式子,并取最大值。但是如果我们走环的另一个方向呢?比如样例中的环从 1 → 4 1\rightarrow 4 14 也可以是 1 → 5 → 4 1\rightarrow 5\rightarrow 4 154。其实也好解决,我们将 a , b a,b a,b 数组再循环复制一遍放到后面,这样 i → j i\rightarrow j ij 的另一个方向就转化为了 j → i + t n j\rightarrow i+tn ji+tn,这是处理环时的常用策略,不理解可以看下面的图。

请添加图片描述

不过要注意,当我们枚举 i i i 的时候, j j j 的取值范围是 [ i + 1 , i + t n − 1 ] [i+1,i+tn-1] [i+1,i+tn1]。否则区间长度大于 t n tn tn 就相当于重复了。

n ≤ 5 × 1 0 5 n\le 5\times 10^5 n5×105,怎么快速算呢。稍微处理一下上面的式子: b i + a i + 1 + a i + 2 + ⋯ + a j − 1 + b j = a 1 + ⋯ + a j − 1 + b j − ( a 1 + ⋯ + a i ) + b i \begin{aligned} &b_i+a_{i+1}+a_{i+2}+\dots+a_{j-1}+b_j\\ =&a_1+\dots+a_{j-1}+b_j-(a_1+\dots+a_i)+b_i \end{aligned} =bi+ai+1+ai+2++aj1+bja1++aj1+bj(a1++ai)+bi如果我们枚举左端点 i i i,那么对一个 i i i,其中的 b i b_i bi a 1 + ⋯ + a i a_1+\dots+a_i a1++ai 就是固定的,我们需要快速确定 j j j,使得上式最大。

我们可以预处理 a a a 的前缀和 s i = a 1 + a 2 + ⋯ + a i s_i=a_1+a_2+\dots+a_i si=a1+a2++ai。上式转化为 s j − 1 + b j − s i + b i s_{j-1}+b_j-s_i+b_i sj1+bjsi+bi,其实我们只需要知道 [ i + 1 , i + t n − 1 ] [i+1,i+tn-1] [i+1,i+tn1] s j − 1 + b j s_{j-1}+b_j sj1+bj 最大值即可。

可以用单调队列动态维护区间最大值。就是滑动窗口问题。或者可以用 s e t set set,时间慢一点,但是不影响。

code1:

拓扑排序

#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <queue>
using namespace std;
const int maxn=5e5+5;
#define pii pair<int,int>
const int inf=1e9;

int n,ans=0;
set<int> g[maxn];
int clean[maxn];

vector<int> ring;
set<int> S;
int to[maxn],twin[maxn];//向环外走可以走到多少1点 以它为中转点向外走两条叉的1点数 
void topu(){//拓扑排序找环,并找到环上点向环外可以走到多少1点 
	queue<int> q;
	for(int u=1;u<=n;u++)
		if(g[u].size()==1)
			q.push(u);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(auto v:g[u]){
			g[v].erase(u);
			twin[v]=max(twin[v],to[v]+to[u]);
			ans=max(ans,twin[v]);
			to[v]=max(to[v],clean[v]+to[u]);
			if(g[v].size()==1)q.push(v);
		}
		g[u].clear();
	}
	
//	for(int u=1;u<=n;u++){
//		cout<<u<<":";
//		for(auto v:g[u])
//			cout<<v<<" ";
//		cout<<endl;
//	}
	
	for(int u=1;u<=n;u++)
		if(g[u].size()>0)
			S.insert(u);
	
	for(int u=*S.begin(),fa=*g[u].begin();;){
		ring.push_back(u);
		for(auto v:g[u]){
			if(v==fa)continue;
			if(v==ring[0])return;
			fa=u;
			u=v;
			break;
		}
	}
}

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>clean[i],to[i]=clean[i];
	for(int i=1,u,v;i<=n;i++){
		cin>>u>>v;
		g[u].insert(v);
		g[v].insert(u);
	}
	
	topu();
	
	int tn=ring.size();
	vector<int> a(tn*2+5),b(tn*2+5),s(tn*2+5);
	for(int i=1;i<=tn;i++){
		a[i]=a[i+tn]=clean[ring[i-1]];
		b[i]=b[i+tn]=to[ring[i-1]];
	}
	for(int i=1;i<=tn*2;i++)s[i]=s[i-1]+a[i];

	deque<pii> dq;//下标 值 
	for(int j=1;j<tn;j++){
		int val=s[j-1]+b[j];
		while(!dq.empty() && dq.back().second<=val)dq.pop_back();
		dq.push_back(pii(j,val));
	}
	for(int i=1,j=tn,val;i<=tn;i++,j++){//i为左端点,j为右端点 
		while(!dq.empty() && dq.front().first<=i)dq.pop_front();
		val=s[j-1]+b[j];
		while(!dq.empty() && dq.back().second<=val)dq.pop_back();
		dq.push_back(pii(j,val));
		
		int max_val=dq.front().second-s[i]+b[i];
		ans=max(ans,max_val);
	}
	
	for(auto u:ring){
		ans=max(ans,twin[u]+s[tn]-clean[u]);
	}
	
	cout<<ans<<endl;
	return 0;
}
/*
9
1 0 1 0 0 1 1 0 1
2 8
2 9
2 5
1 5
1 3
1 4
4 5
4 6
6 7

*/
code2:

d f s dfs dfs

#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <queue>
#include <algorithm>
#include <ctime>
using namespace std;
const int maxn=5e5+5;
const int inf=1e9;
#define pii pair<int,int>
//#define int long long

int n,ans=0;
set<int> g[maxn];

int clean[maxn];
int to[maxn];//向环外走可以走到多少1点 

vector<int> cir;
bool incir[maxn];

vector<int> stk;
bool vis[maxn];
bool find_cir(int u,int fa){
//	cout<<u<<' '<<fa<<endl;
	vis[u]=true;
	stk.push_back(u);
	for(auto v:g[u]){
		if(fa==v)continue;
		if(vis[v]){
			while(true){
				int t=stk.back();
				stk.pop_back();
				incir[t]=true;
				cir.push_back(t);
				if(t==v)break;
			}
			return true;
		}
		if(find_cir(v,u))return true;
	}
	stk.pop_back();
	vis[u]=false;
	return false;
}

void dfs(int u,int fa){
	for(auto v:g[u]){
		if(incir[v] || fa==v)continue;
		dfs(v,u);
		ans=max(ans,to[v]+to[u]);
		to[u]=max(to[u],to[v]+clean[u]);
	}
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>clean[i],to[i]=clean[i];
	for(int i=1,u,v;i<=n;i++){
		cin>>u>>v;
		g[u].insert(v);
		g[v].insert(u);
	}
	
	find_cir(1,-1);
	
	for(auto u:cir)dfs(u,-1);
	
	int tn=cir.size();
	vector<int> a(tn*2+5),b(tn*2+5),s(tn*2+5);
	for(int i=1;i<=tn;i++){
		a[i]=a[i+tn]=clean[cir[i-1]];
		b[i]=b[i+tn]=to[cir[i-1]];
	}
	for(int i=1;i<=tn*2;i++)s[i]=s[i-1]+a[i];
	
	set<pii> S;
	for(int j=1;j<tn;j++){
		int val=s[j-1]+b[j];
		S.insert(pii(val,j));
	}
	for(int i=1,j=tn,val;i<=tn;i++,j++){//i为左端点,j为右端点 
		val=s[j-1]+b[j];
		S.insert(pii(val,j));
		while(!S.empty() && (*S.rbegin()).second<=i)S.erase(*S.rbegin());
		
		int max_val=(*S.rbegin()).first-s[i]+b[i];
		ans=max(ans,max_val);
	}
	
	int cnt=s[tn];//环上所有1点和 
//	cout<<cnt<<endl;
	for(auto u:cir){
		int mx1=-inf,mx2=-inf;
		for(auto v:g[u]){
			if(incir[v])continue;
			if(to[v]>mx1){
				mx2=mx1;
				mx1=to[v];
			}
			else if(to[v]>mx2){
				mx2=to[v];
			}
		}
//		cout<<"***"<<u<<" "<<mx1<<" "<<mx2<<endl;
		ans=max(ans,cnt+mx1+mx2);
	}
	
	cout<<ans<<endl;
	return 0;
}
/*
9
1 0 1 0 0 1 1 0 1
2 8
2 9
2 5
1 5
1 3
1 4
4 5
4 6
6 7

*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文学习:《通过基于元学习的图变换探索冷启动场景下的药物-靶标相互作用预测》

原文标题&#xff1a;Exploring drug-target interaction prediction on cold-start scenarios via meta-learning-based graph transformer 原文链接&#xff1a;https://www.sciencedirect.com/science/article/pii/S1046202324002470 药物-靶点相互作用&#xff08;DTI&…

十八、TCP多线程、多进程并发服务器

1、TCP多线程并发服务器 服务端&#xff1a; #include<stdio.h> #include <arpa/inet.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <pthread.h>…

AIGC-文生图与图生图

在之前的文章中&#xff0c;我们知道了如何通过Web UI和Confy UI两种SD工具来进行图片生成&#xff0c;今天进一步地讲解其中的参数用处及如何调节。 文生图 参数详解 所谓文生图&#xff0c;就是通过文字描述我们想要图片包含的内容。初学的话&#xff0c;还是以Web UI为例…

量化交易 - 聚宽joinquant - 多因子入门研究 - 源码开源

先看一下我们的收益&#xff1a; JoinQuant直达这里看看 下面讲解原理和代码。 目录 一、是否为st 二、是否停牌 三、市值小、roe大 四、编写回测代码 今天来研究一下多因子回测模型&#xff0c;这里以‘市值’、‘roe’作为例子。 几个标准&#xff1a;沪深300里选股&am…

FPGA 37 ,FPGA千兆以太网设计实战:RGMII接口时序实现全解析( RGMII接口时序设计,RGMII~GMII,GMII~RGMII 接口转换 )

目录 前言 一、设计流程 1.1 需求理解 1.2 模块划分 1.3 测试验证 二、模块分工 2.1 RGMII→GMII&#xff08;接收方向&#xff0c;rgmii_rx 模块&#xff09; 2.2 GMII→RGMII&#xff08;发送方向&#xff0c;rgmii_tx 模块&#xff09; 三、代码实现 3.1 顶层模块 …

上篇:《排序算法的奇妙世界:如何让数据井然有序?》

个人主页&#xff1a;strive-debug 排序算法精讲&#xff1a;从理论到实践 一、排序概念及应用 1.1 基本概念 **排序**&#xff1a;将一组记录按照特定关键字&#xff08;如数值大小&#xff09;进行递增或递减排列的操作。 1.2 常见排序算法分类 - **简单低效型**&#xff…

红宝书第三十四讲:零基础学会单元测试框架:Jest、Mocha、QUnit

红宝书第三十四讲&#xff1a;零基础学会单元测试框架&#xff1a;Jest、Mocha、QUnit 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、单元测试是什么&#xff1f; 就像给代码做“体检”&#xff0c;帮你检查…

CST1019.基于Spring Boot+Vue智能洗车管理系统

计算机/JAVA毕业设计 【CST1019.基于Spring BootVue智能洗车管理系统】 【项目介绍】 智能洗车管理系统&#xff0c;基于 Spring Boot Vue 实现&#xff0c;功能丰富、界面精美 【业务模块】 系统共有三类用户&#xff0c;分别是&#xff1a;管理员用户、普通用户、工人用户&…

HTTP:五.WEB服务器

web服务器 定义:实现提供资源或应答的提供者都可以谓之为服务器!web服务器工作内容 接受建立连接请求 接受请求 处理请求 访问报文中指定的资源 构建响应 发送响应 记录事务处理过程 Web应用开发用到的一般技术元素 静态元素:html, img,js,Css,SWF,MP4 动态元素:PHP,…

0基础 | 硬件滤波 C、RC、LC、π型

一、滤波概念 &#xff08;一&#xff09;滤波定义 滤波是将信号中特定波段频率滤除的操作&#xff0c;是抑制和防止干扰的重要措施。通过滤波器实现对特定频率成分的筛选&#xff0c;确保目标信号的纯净度&#xff0c;提升系统稳定性。 &#xff08;二&#xff09;滤波器分…

图论基础理论

在我看来&#xff0c;想要掌握图的基础应用&#xff0c;仅需要三步走。 什么是图&#xff08;基本概念&#xff09;、图的构造&#xff08;打地基&#xff09;、图的遍历方式&#xff08;应用的基础&#xff09; 只要能OK的掌握这三步、就算图论入门了&#xff01;&#xff0…

企业级低代码平台的架构范式转型研究

在快速迭代的数字时代&#xff0c;低代码平台如同一股清流&#xff0c;悄然成为开发者们的新宠。 它利用直观易用的拖拽式界面和丰富的预制组件&#xff0c;将应用程序的开发过程简化到了前所未有的程度。通过封装复杂的编程逻辑和提供强大的集成能力&#xff0c;低代码平台让…

怎么免费下载GLTF/GLB格式模型文件,还可以在线编辑修改

​ 现在非常流行glb格式模型&#xff0c;和gltf格式文件&#xff0c;可是之类模型网站非常非常少 1&#xff0c;咱们先直接打开http://glbxz.com 官方glb下载网站 glbxz.com 2 可以搜索&#xff0c;自己想要的模型关键词 3&#xff0c;到自己想下载素材页面 4&#xff0c;…

大模型到底是怎么产生的?一文揭秘大模型诞生全过程

前言 大模型到底是怎么产生的呢? 本文将从最基础的概念开始,逐步深入,用通俗易懂的语言为大家揭开大模型的神秘面纱。 大家好,我是大 F,深耕AI算法十余年,互联网大厂核心技术岗。 知行合一,不写水文,喜欢可关注,分享AI算法干货、技术心得。 【专栏介绍】: 欢迎关注《…

2025年3月 Scratch图形化三级 真题解析 中国电子学会全国青少年软件编程等级考试

2025年3月Scratch图形化编程等级考试三级真题试卷 一、选择题 第 1 题 默认小猫角色&#xff0c;scratch运行程序后&#xff0c;下列说法正确的是&#xff1f;&#xff08; &#xff09; A.小猫的颜色、位置在一直变化 B.小猫在舞台中的位置在一直变化&#xff0c;颜色…

【贪心之摆动序列】

题目&#xff1a; 分析&#xff1a; 这里我们使用题目中给的第二个实例来进行分析 题目中要求我们序列当中有多少个摆动序列&#xff0c;摆动序列满足一上一下&#xff0c;一下一上&#xff0c;这样是摆动序列&#xff0c;并且要输出摆动序列的最长长度 通过上面的图我们可以…

0x25广度优先搜索+0x26广搜变形

1.一般bfs AcWing 172. 立体推箱子 #include<bits/stdc.h> using namespace std; int n,m; char s[505][505]; int vis[3][505][505]; int df[3][4]{{1,1, 2,2},{0,0,1,1}, {0,0,2,2}}; int dx[3][4]{{0,0,1,-2},{0,0,1,-1},{2,-1,0,0}}; int dy[3][4]{{1,-2,0,0},{2,…

java面向对象02:回顾方法

回顾方法及加深 定义方法 修饰符 返回类型 break&#xff1a;跳出switch和return的区别 方法名 参数列表 package com.oop.demo01;//Demo01类 public class Demo01 {//main方法public static void main(String[] args) {}/*修饰符 返回值类型 方法名(...){//方法体return…

数据结构day05

一 栈的应用&#xff08;括号匹配&#xff09; 各位同学大家好&#xff0c;在之前的小结中&#xff0c;我们学习了栈和队列这两种数据结构&#xff0c;那从这个小节开始&#xff0c;我们要学习几种栈和队列的典型应用。这个小节中&#xff0c;我们来看一下括号匹配问题&#xf…

windows中搭建Ubuntu子系统

windows中搭建虚拟环境 1.配置2.windows中搭建Ubuntu子系统2.1windows配置2.1.1 确认启用私有化2.1.2 将wsl2设置为默认版本2.1.3 确认开启相关配置2.1.4重启windows以加载更改配置 2.2 搭建Ubuntu子系统2.2.1 下载Ubuntu2.2.2 迁移位置 3.Ubuntu子系统搭建docker环境3.1安装do…