蓝桥杯14届国赛C++A组
1.圆上的连线
本题如果直接计数考虑起来十分复杂,我们考虑使用递推法
设 a n a_{n} an表示的是圆上点的个数为n时的方案数,则我们考虑点1与其他的点相连的所有可能情况:
1.点1不与任何点相连,此时的可能情况与 a n − 1 a_{n-1} an−1时相同
2.若点1与点i相连,则1-i这条直线将原本的圆分成两部分,总的方案数为这两部分方案数的乘积
因此
a n = a n − 1 + ∑ k = 0 n − 2 a k a n − 2 − k a_{n}=a_{n-1}+\sum_{k=0}^{n-2}a_{k}a_{n-2-k} an=an−1+k=0∑n−2akan−2−k
直接递推求解即可
答案为104
2.2023次方
本题考察的是数论知识
由(2,2023)=1,故
2 3 ⋯ 2023 m o d 2023 2^{3^{\cdots ^{2023}}} mod 2023 23⋯2023mod2023= 2 3 ⋯ 2023 m o d ϕ ( 2023 ) 2^{3^{\cdots ^ {2023} }mod \phi(2023)} 23⋯2023modϕ(2023)
考虑到 ϕ ( 2023 ) = 1632 = 32 × 3 × 17 \phi(2023)=1632=32\times 3\times 17 ϕ(2023)=1632=32×3×17
而 3 ⋯ 2023 ≡ 0 ( m o d 3 ) 3^{\cdots ^{2023}} \equiv 0 \pmod 3 3⋯2023≡0(mod3)
3 ⋯ 2023 ≡ 1 ( m o d 32 ) 3^{\cdots ^{2023}} \equiv 1 \pmod {32} 3⋯2023≡1(mod32)
3 ⋯ 2023 ≡ 1 ( m o d 17 ) 3^{\cdots ^{2023}} \equiv 1 \pmod {17} 3⋯2023≡1(mod17)
故我们只需用中国剩余定理求解即可
最终可知 3 ⋯ 2023 ≡ 1089 ( m o d 1632 ) 3^{\cdots ^{2023}}\equiv 1089 \pmod {1632} 3⋯2023≡1089(mod1632)
cpp
//使用该代码进行求解即可
int x=1;
for (int i=1;i<=1089;i++){
x=x*2%2023;
}
答案为869
3.切割
本题较为简单,就是求出w,h的最小的公共素因子p,然后计算w/p*h/p就可以
本题会有一个比较卡的点具体可以看代码
cpp
//90%AC
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<ll,ll > PII;
vector<PII > p;
ll w,h;
ll get_prime(){
ll copy=w;
for (ll i=2;i<=w;i++){
if (w%i==0){
if (h%i==0){
w=copy;
return i;
}
while (w%i==0){
w/=i;
}
}
}
w=copy;
return 0;
}
int m[2500];
int main(){
cin>>w>>h;
if (get_prime()==0){//这样的写法结合下面的else,我们把get_prime计算了两边,从测评结果来看这回导致一个点TLE
cout<<0<<endl;
}
else {
ll x=get_prime();
cout<<((w/x)*(h/x));
}
return 0;
}
cpp
//100%AC
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<ll,ll > PII;
vector<PII > p;
ll w,h;
ll get_prime(){
ll copy=w;
for (ll i=2;i<=w;i++){
if (w%i==0){
if (h%i==0){
w=copy;
return i;
}
while (w%i==0){
w/=i;
}
}
}
w=copy;
return 0;
}
int m[2500];
int main(){
cin>>w>>h;
ll x=get_prime();
if (x==0){
cout<<0<<endl;
}
else {
cout<<((w/x)*(h/x));
}
return 0;
}//这样写就不会TLE
4.XYZ
本题就是一个简单的推式子的问题,需要注意的点是需要避免因为负负得正导致出现问题。详情可以见下面代码:
cpp
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<ll,ll > PII;
int T;
int main(){
cin>>T;
while (T--){
ll l,r;
cin>>l>>r;
ll ans=0;
if (r-2*l+1<=0){
cout<<0<<endl;
continue;
}
ans=max((ll)0,(r-2*l+2)*(r-2*l+1)/2);//注意这个坑点,这里一定有可能回出现负负得正的情况导致错误
cout<<ans<<endl;
}
return 0;
}
5.第k小的和
本题是一个经典的换维思考来降低复杂度的问题
如果我们直接算出所有a_i+b_j的值,我们就需要进行O(n^2)次操作,这样一定会超时
一个自然的优化是我们考虑进行二分答案,然后在check每个点是否可能成立的时候,我们再用upper_bound,lower_bound这种log级别的查找技巧来降低复杂度
cpp
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<set>
//一定要注意long long 的问题
using namespace std;
typedef long long ll;
typedef pair<ll,ll > PII;
long long n,m,k;
vector<long long > a;
vector<long long > b;
bool check(long long x){
//check用于判断是否有大于等于k个组合小于x
long long num=0;
for (int i=0;i<a.size();i++){
num+=upper_bound(b.begin(),b.end(),x-a[i])-b.begin();
//cout<<i<<' '<<b.end()-lower_bound(b.begin(),b.end(),x-a[i])<<endl;
}
//cout<<"xduiying de "<<x<<' '<< num<<endl;
if (num>=k){
return true;
}
return false;
}
int main(){
cin>>n>>m>>k;
int x=0;
int y=0;
int z=0x3f3f3f3f;
for (int i=1;i<=n;i++){
int input;
cin>>input;
a.push_back(input);
z=min(z,input);
x=max(x,input);
}
for (int i=1;i<=m;i++){
int input;
cin>>input;
b.push_back(input);
y=max(y,input);
}
sort(b.begin(),b.end());
long long l=z+(*b.begin());
long long r=x+y;
while (l<r){
long long mid=(l+r)>>1;
if (check(mid)){
//如果存在大于等于k个组合的和小于等于x
r=mid;
}
else {
l=mid+1;
}
}
cout<<l<<endl;
return 0;
}
6.相连的边
欲解决该题,我们需要通过观察发现如下事情:
题目中所选出的三条边,事实上只有如下两种情况:
(1)链状
对于该种情况,我们对于只需要对于每个节点进行DFS(只搜索3层)
(2)三叉戟状
对于该种情况我们只需考虑每个点的所有临边中边权最大的三条边即可
因此对于所有的点,我们存储所有与其相连的边,并按照边权大小对其进行排序。
我们上述排序操作的时间复杂度为 ∑ O ( D l o g D ) \sum O(DlogD) ∑O(DlogD)D为每个节点的度数
严格小于O(nlogn)
处理完排序问题后,我们只需O(n)就可以处理三叉戟形状的问题。
下面我们来考虑如果直接采用DFS,链状情况的时间复杂度
我们首先锁定一条中心边(U,V),则以他为中i性能的长度为3的路径共有:
( D U − 1 ) ( D V − 1 ) (D_{U}-1)(D_{V}-1) (DU−1)(DV−1)
因此我们在DFS中所遍历的所有边的数量为:
∑ ( U , V ) ( D U − 1 ) ( D V − 1 ) ( ∗ ) \sum_{(U,V)}(D_{U}-1)(D_{V}-1)(*) (U,V)∑(DU−1)(DV−1)(∗)
下面我们考虑如何计算(*)的极值:
我们直接进行代数放缩:
考虑到树没有奇圈,因此树一定是二部图
故我们可以将树的顶点集分为X,Y两部分
故
∑ ( U , V ) 邻接 ( D U − 1 ) ( D V − 1 ) ≤ ∑ x ∈ X , y ∈ Y ( D x − 1 ) ( D y − 1 ) = ( ∑ x ∈ X ( D x − 1 ) ) ( ∑ y ∈ Y ( D y − 1 ) ) ≤ ( ∑ x ∈ X D x − 1 + ∑ y ∈ Y D y − 1 2 ) 2 ( ∗ ) 由握手定理可知 ∑ a ∈ G D a = 2 ( n − 1 ) 由 G 是树可知 , ∣ X ∣ + ∣ Y ∣ = n 故 ( ∗ ) = ( n − 2 ) 2 4 显然上述等号均可以取等 \sum_{(U,V)邻接}(D_{U}-1)(D_{V}-1)\leq \sum_{x\in X,y\in Y}(D_{x}-1)(D_{y}-1)\\ =(\sum_{x\in X}(D_{x}-1))(\sum_{y\in Y}(D_{y}-1))\\ \leq (\frac{\sum_{x\in X}D_{x}-1+\sum_{y\in Y} D_{y}-1}{2})^2(*)\\ 由握手定理可知\sum_{a\in G}D_{a}=2(n-1)\\ 由G是树可知,|X|+|Y|=n\\ 故(*)=\frac{(n-2)^2}{4}\\ 显然上述等号均可以取等 (U,V)邻接∑(DU−1)(DV−1)≤x∈X,y∈Y∑(Dx−1)(Dy−1)=(x∈X∑(Dx−1))(y∈Y∑(Dy−1))≤(2∑x∈XDx−1+∑y∈YDy−1)2(∗)由握手定理可知a∈G∑Da=2(n−1)由G是树可知,∣X∣+∣Y∣=n故(∗)=4(n−2)2显然上述等号均可以取等
因此我们如果采用DFS来解题,最坏时间复杂度可以达到O(n^2)
如果n的范围达到了2e5是很有可能超时的
但是在实际进行代码编写的过程中,我们不难发现
其实我们对于固定的邻接的u,v我们只需考虑u的除了uv以外的所有边中最长的边和v的除了uv以外最长的边
这样考虑我们其实就选出了长度和最长的三条边
cpp
//60%AC,只写了最简单情况的剪枝
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
const int N=4e5+10;
typedef pair<long long ,int > PII;
typedef long long ll;
vector<PII > a [N];
int n;
ll ans;
int main(){
cin>>n;
for (int i=1;i<=n-1;i++){
ll id,w;
cin>>id>>w;
a[i+1].push_back({w,id});
a[id].push_back({w,i+1});
}
for (int i=1;i<=n;i++){
sort(a[i].begin(),a[i].end(),greater<PII >() );
if (a[i].size()<3){
continue;
}
else {
ans=max(ans,a[i][0].first+a[i][1].first+a[i][2].first);
}
}
for (int i=1;i<=n;i++){
if (a[i].size()<=1){
continue;
}
for (int t=0;t<=a[i].size();t++){
ll res=0;
//cout<<i<<endl;
//cout<<"helloworld";
if (a[i][t].second<i){
continue;
} //为了避免重复计算,我们只考虑i的序号较小的情况
if (a[a[i][t].second].size()<=1){
continue;
}
res+=a[i][t].first;//加上i,j连边的边权
if (t==0){
res+=a[i][1].first;
}
else {
res+=a[i][0].first;
}
if (i==a[a[i][t].second][0].second){
res+=a[a[i][t].second][1].first;
}
else {
res+=a[a[i][t].second][0].first;
}
ans=max(ans,res);
}
}
cout<<ans<<endl;
return 0;
}
7.01游戏
本题考察的就是DFS加剪枝,属于纯纯代码能力的考察
cpp
//20:05
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
const int N=120;
typedef pair<long long ,int > PII;
typedef long long ll;
int n;
ll ans;
int g[N][N];
int cnt1;
int cnt0;
bool col(int x,int y){
for (int i=1;i<=n;i++){
if (g[i][x]!=g[i][y]){
return false;
}
}
return true;
}
bool check(){
for (int i=1;i<=n;i++){
for (int j=1;j<=n-2;j++){
if (g[i][j]==g[i][j+1]&&g[i][j]==g[i][j+2]){
return false;
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n-2;j++){
if (g[j][i]==g[j+1][i]&&g[j][i]==g[j+2][i]){
return false;
}
}
}
for (int i=1;i<=n;i++){
int sum=0;
for (int j=1;j<=n;j++){
sum+=g[i][j];
}
if (sum!=n/2){
return false;
}
}
for (int i=1;i<=n;i++){
int sum=0;
for (int j=1;j<=n;j++){
sum+=g[j][i];
}
if (sum!=n/2){
return false;
}
}
for (int i=1;i<=n;i++){
for (int j=i+1;j<=n;j++){
if (g[i]==g[j]){
return false;
}
}
}
for (int i=1;i<=n;i++){
for (int j=i+1;j<=n;j++){
if (col(i,j)){
return false;
}
}
}
return true;
}
void print(){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
cout<<g[i][j];
}
cout<<endl;
}
}
void dfs(int now ,int t,int idx,int idx0){
if (idx+cnt1>n*n/2){
return ;
}
if (idx0+cnt0>n*n/2){
return ;
}
int x=(now-1)%n+1;
int y=(now-1)/n+1;
if (g[x][y]==-1){
g[x][y]=t;
if (now==n*n){
if (check()){
print();
}
g[x][y]=-1;
}
else {
if (g[now%n+1][now/n+1]!=-1){
dfs(now+1,0,idx,idx0);
g[x][y]=-1;
}
else {
dfs(now+1,0,idx,idx0+1);
dfs(now+1,1,idx+1,idx0);
g[x][y]=-1;
}
}
}
else {
if (now==n*n){
if (check()){
print();
}
}
else {
if (g[now%n+1][now/n+1]!=-1){
dfs(now+1,0,idx,idx0);
}
else {
dfs(now+1,0,idx,idx0+1);
dfs(now+1,1,1+idx,idx0);
}
}
}
}
int main(){
cin>>n;
for (int i=1;i<=n;i++){
string s;
cin>>s;
for (int j=0;j<s.size();j++){
if (s[j]=='1'){
g[i][j+1]=1;
cnt1++;
}
else if (s[j]=='0'){
g[i][j+1]=0;
cnt0++;
}
else {
g[i][j+1]=-1;
}
}
}
//cout<<check()<<endl;
if (g[1][1]!=-1){
dfs(1,0,0,0);
}
else {
dfs(1,0,0,1);
dfs(1,1,1,0);
}
return 0;
}
8.子串
本题正解比较复杂,这里给出字符串Hash的解法
cpp
//50%AC
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=2e6+10;
ull h[N];
ull p[N];
const ll P=131;
string s;
ull get_hash(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
void init(){
p[0]=1;
for (int i=1;i<=s.size();i++){
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+s[i-1];
}
}
ll ans[N];
int main(){
cin>>s;
init();
for (int len=1;len<=s.size();len++){
vector<ull > hash;
for (int i=1;i+len-1<=s.size();i++){
hash.push_back(get_hash(i,i+len-1));
}
sort(hash.begin(),hash.end());
int cnt=1;
for (int i=0;i<hash.size();i++){
if (hash[i]==hash[i+1]&&i+1<hash.size()){
cnt++;
}
else {
ans[cnt]++;
cnt=1;
}
}
}
for (int i=1;i<=s.size();i++){
cout<<ans[i]<<endl;
}
return 0;
}
9.树上的路径
本题正解较为复杂,笔者采用的解法是直接对每个节点作为根节点进行DFS,计算出所有路径的长度
cpp
//45%AC
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+10;
int n;
int l,r;
ll ans;
ll depth[N];
int h[N],e[N],ne[N],idx;
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void init(){
idx=0;
memset(h,-1,sizeof h);
}
void dfs(int now,int pa){
if (pa==-1){
depth[now]=0;
}
else
depth[now]=depth[pa]+1;
if (l<=depth[now]&&depth[now]<=r){
ans+=depth[now];
//cout<<now<<' '<< depth[now]<<endl;
}
if (depth[now]>r){
return ;
}
for (int i=h[now];i!=-1;i=ne[i]){
int j=e[i];
if (j==pa){
continue;
}
dfs(j,now);
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>l>>r;
init();
for (int i=1;i<=n-1;i++){
int input;
cin>>input;
add(input,i+1);
add(i+1,input);
}
for (int i=1;i<=n;i++){
memset(depth,0,sizeof depth);
dfs(i,-1);
}
cout<<ans/2<<endl;
return 0;
}
10.
cpp
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30;
int n, m, hp;
bool vt[N]; // 记录怪物是否被击杀
int v[N];
int h[N], e[N], ne[N], w[N];
int idx = 0;
void init() {
memset(h, -1, sizeof h);
}
void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
int ans = 0x3f3f3f3f;
// now: 当前位置
// hurt: 当前受到的伤害
// time: 当前消耗的时间
// killed: 已经击杀的怪物数量
// path_vis: 自从上一次击杀怪物后,走过的节点集合(位运算压缩,用于防止死循环)
void dfs(int now, int hurt, int time, int killed, int path_vis) {
// 剪枝 1:血量不够,或者时间已经比之前找的答案慢了,直接回头
if (hurt >= hp || time >= ans) return;
// 终点判断:杀满了 n 只怪,并且人刚好站在终点
if (killed == n && now == n - 1) {
ans = min(ans, time);
return;
}
// ==========================================
// 动作 1:选择【杀怪】(前提是脚下的怪还没死)
// ==========================================
if (!vt[now]) {
int dmg = 0;
// 统计周边还活着的怪物带来的伤害
for (int i = h[now]; i != -1; i = ne[i]) {
int neighbor = e[i];
if (!vt[neighbor]) {
dmg += v[neighbor];
}
}
vt[now] = true; // 拔刀击杀!
// 杀完之后进入下一层!
// 【核心细节】:杀完怪了,局面刷新,把 path_vis 重新清空,只记录当前点 (1 << now)
// 这样就可以允许小蓝走回头路去探索了!
dfs(now, hurt + dmg, time, killed + 1, 1 << now);
vt[now] = false; // 回溯,把怪复活
}
// ==========================================
// 动作 2:选择【移动】(单纯路过,不杀脚下的怪)
// ==========================================
for (int i = h[now]; i != -1; i = ne[i]) {
int j = e[i];
// 为了防止小蓝在不杀怪的情况下无限绕圈,必须检查 j 是否在这一次的局部路径中走过
if (!(path_vis & (1 << j))) {
// 走向 j,把 j 加入局部路径记忆中
dfs(j, hurt, time + w[i], killed, path_vis | (1 << j));
}
}
}
int main() {
cin >> n >> m >> hp;
init();
for (int i = 0; i < n; i++) cin >> v[i];
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c); // 无向图双向建边
}
// 初始状态:站在0点,受0伤,耗0时,杀0怪,局部路径里只有0号点 (1<<0)
dfs(0, 0, 0, 0, 1 << 0);
if (ans == 0x3f3f3f3f) {
cout << -1 << "\n";
} else {
cout << ans << "\n";
}
return 0;
}