当前位置: 首页 > article >正文

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

昨天打完蓝桥杯本来想写个 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上插入cs=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;//101001010011101100010110011001100110break;}}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;
}
/*
1225897856*/

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 12
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 longint 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*/

相关文章:

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

昨天打完蓝桥杯本来想写个 p y t h o n python python A A A 组的题解&#xff0c;结果被队友截胡了。今天上课把 C A CA CA 组的题看了&#xff0c;感觉挺简单的&#xff0c;所以来水一篇题解。 这场 B B B 是一个爆搜&#xff0c; C C C 利用取余的性质比较好写&#…...

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

原文标题&#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&…...

【题解-洛谷】P1824 进击的奶牛

题目:P1824 进击的奶牛 题目描述 Farmer John 建造了一个有 N N N( 2 ≤ N ≤...

机械革命 无界15X 自带的 有线网卡 YT6801 debian12下 的驱动方法

这网卡是国货啊。。。 而且人家发了驱动程序 Motorcomm Microelectronics. YT6801 Gigabit Ethernet Controller [1f0a:6801] 网卡YT6801在Linux环境中的安装方法 下载网址 yt6801-linux-driver-1.0.29.zip 我不知道别的系统是否按照说明安装就行了 但是debian12不行&…...

十八、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>…...

JAVA中正则表达式的入门与使用

JAVA中正则表达式的入门与使用 一&#xff0c;基础概念 正则表达式&#xff08;Regex&#xff09; 用于匹配字符串中的特定模式&#xff0c;Java 中通过 java.util.regex 包实现&#xff0c;核心类为&#xff1a; Pattern&#xff1a;编译后的正则表达式对象。 Matcher&#…...

AIGC-文生图与图生图

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

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

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

本地缓存方案Guava Cache

Guava Cache 是 Google 的 Guava 库提供的一个高效内存缓存解决方案&#xff0c;适用于需要快速访问且不频繁变更的数据。 // 普通缓存 Cache<Key, Value> cache CacheBuilder.newBuilder().maximumSize(1000) // 最大条目数.expireAfterWrite(10, TimeUnit.MINUTES) /…...

虚拟列表react-virtualized使用(npm install react-virtualized)

1. 虚拟化列表 (List) // 1. 虚拟化列表 (List)import { List } from react-virtualized; import react-virtualized/styles.css; // 只导入一次样式// 示例数据 const list Array(1000).fill().map((_, index) > ({id: index,name: Item ${index},description: This is i…...

解释型语言和编译型语言的区别

Python 的执行过程通常涉及字节码&#xff0c;而不是直接将代码编译为机器码。以下是详细的解释&#xff1a; ### **Python 的执行过程** 1. **源代码到字节码**&#xff1a; - Python 源代码&#xff08;.py 文件&#xff09;首先被编译为字节码&#xff08;.pyc 文件&…...

猫咪如厕检测与分类识别系统系列【三】融合yolov11目标检测

✅ 前情提要 家里养了三只猫咪&#xff0c;其中一只布偶猫经常出入厕所。但因为平时忙于学业&#xff0c;没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关&#xff0c;频繁如厕可能是泌尿问题&#xff0c;停留过久也可能是便秘或不适。为了更科学地了解牠…...

sql server 字段逗号分割取后面的值

在 SQL Server 中&#xff0c;如果你有一个字段&#xff08;字段类型通常是字符串&#xff09;&#xff0c;其中包含用逗号分隔的值&#xff0c;并且你想提取这些值中逗号后面的特定部分&#xff0c;你可以使用多种方法来实现这一点。这里我将介绍几种常见的方法&#xff1a; …...

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;帮你检查…...

【JDBC-54.1】MySQL JDBC连接字符串常用参数详解

在Java应用程序中连接MySQL数据库时&#xff0c;JDBC连接字符串是建立连接的关键。一个配置得当的连接字符串不仅能确保连接成功&#xff0c;还能优化性能、增强安全性并处理各种连接场景。本文将深入探讨MySQL JDBC连接字符串的常用参数及其最佳实践。 1. 基本连接字符串格式…...

swagger 注释说明

一、接口注释核心字段 在 Go 的路由处理函数&#xff08;Handler&#xff09;上方添加注释&#xff0c;支持以下常用注解&#xff1a; 注解名称用途说明示例格式Summary接口简要描述Summary 创建用户Description接口详细说明Description 通过用户名和邮箱创建新用户Tags接口分…...

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

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

【前端网络请求】XHR封装,支持文件上传、进度监控、混合字段传输

网络请求介绍 XMLHttpRequest(XHR)是前端开发中用于发起网络请求的基础技术。虽然现代开发中常用fetch或axios,但掌握XHR的封装技巧仍能让你更灵活地应对复杂需求。本文将通过一个可复用、功能全面的XHR封装工具,教你实现以下功能: 📤 文件上传(单个/多个文件)📊 实…...

# Shell脚本参数设计规范(DeepSeek指导)

Shell脚本参数设计规范&#xff08;DeepSeek指导&#xff09; 文章目录 Shell脚本参数设计规范&#xff08;DeepSeek指导&#xff09;A 我问&#xff1a;Q DeepSeek回答&#xff1a;**命令行参数表示规范****标准化表示示例**情况1&#xff1a;必选选项参数值情况2&#xff1a;…...

学习SqlSugar的跨库查询基本用法

使用SqlSugar操作数据库通常都是单库操作&#xff0c;跨库查询的情况要么是单个系统数据不完整&#xff0c;需要其它系统的关联业务数据支撑&#xff0c;要么就是需要整合汇总多个系统的数据进行数据数据分析、处理、展示。遇到上述情况&#xff0c;可以要求另外的系统提供查询…...

HTTP:五.WEB服务器

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

5.3 GitHub订阅系统核心架构解密:高并发设计与SQLite优化实战

GitHub Sentinel 分析报告功能实现:订阅管理核心逻辑解析 关键词:GitHub API 订阅管理, SQLite 数据库设计, RESTful API 开发, 原子操作封装, 异常处理机制 1. 订阅管理功能架构设计 订阅管理模块采用分层架构设计,通过清晰的接口隔离实现高内聚低耦合: #mermaid-svg-bW…...

CSI-PVController-volumeWorker

volumeWorker() 与claim worker流程一样&#xff0c;从volumeQueue中取数据&#xff0c;也就是取出的都是PV&#xff0c;如果informer中有这个pv&#xff0c;就进入update流程。 定义workFunc&#xff1a;首先&#xff0c;定义了一个匿名函数workFunc&#xff0c;这个函数是实…...

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

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

图论基础理论

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

leaflet 之 获取中国某个行政区的经纬度边界(latLngBounds)

思路 在json文件中获取下面的四个点 组成东北,西南两组 { “southwest”: { “lat”: 35.950, “lng”: 120.000 },//西南方 “northeast”: { “lat”: 36.200, “lng”: 120.300 }//东北方 } 最西点经度&#xff08;minLng&#xff09; 最东点经度&#xff08;maxLng&#x…...

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

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

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

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