目录
菜狗的做题心得,欢迎大家批评指正
F-小苯的回文询问_牛客周赛 Round 38 (nowcoder.com)
题目解释:
查询区间是否存在长度大于2的回文子序列
解题思路:
考察:标记
根据题意,这题的意思是区间内是否存在长度为3的回文子串
用一个数组表示对于位置i的前面存在着回文子序列的位置的下标即用来记录能够构成好数组的并且离当前位置最近的下标的位置。
用map数组来记录离数字k最近的下标位置。对于当前的每个数,我们都往前查找一下,最近的相同数组的下标。判断只要不是相邻的位置便满足要求。若是存在但是不满足长度大于2的要求,则查找次相邻数组的标记能构成子序列的下标位置。然后注意如果有三个相邻的数字,map更新的时候覆盖了,所以需要特判一下
参考链接:(2条未读私信) 【题解】牛客周赛 Round 38_ICPC/CCPC/NOIP/NOI刷题训练题单_牛客竞赛OJ (nowcoder.com)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
map<int,int> mp;
int d[N];
int main()
{
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(mp[a[i]]==0)
{
mp[a[i]]=i;
}else
{
if(i-mp[a[i]]>1)
{
d[i]=mp[a[i]];
mp[a[i]]=i;
}else
{
d[i]=d[mp[a[i]]];
mp[a[i]]=i;
if(a[i]==a[i-2])
{
d[i]=max(d[i],i-2);
}
}
}
d[i]=max(d[i],d[i-1]);
}
//for(int i=1;i<=n;i++)
//cout<<d[i]<<" ";
//cout<<endl;
//
//
while(q--)
{
int l,r;
cin>>l>>r;
if(d[r]>=l)
cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
原题链接:0质数拆分 - 蓝桥云课 (lanqiao.cn)
题目解释:
2022可以拆分成多少个不同质数的和
解题思路:
考察:暴力 素数筛 01背包
第一种解法是暴力求解,虽然还是不太懂其中原理哈
任何大于6的偶数可以表示为两素数之和
#include<bits/stdc++.h>
using namespace std;
int check(int x)
{
if(x<2) return 0;
for(int i=2;i<=x/i;i++)
{
if(x%i==0) return 0;
}
return 1;
}
int main()
{
int sum=0;
int x=2022;
for(int i=2;i<=x;i++)
{
if(check(i))
{
x-=i; sum++;
}
if(x==0) break;
}
cout<<sum;
}
第二种方法是 素数筛+01背包
#include<bits/stdc++.h>
using namespace std;
int st[2025]={0};
vector<int> primes;
int dp[2025][2025];//dp[i][j] 表示取到第i个质数 组成j的最大质数的个数
void prime(int x)
{
int cnt;
for(int i=2;i<=x;i++)
{
if(st[i]) continue;
primes.push_back(i);
for(int j=i+i;j<=x;j+=i)
{
st[j]=1;
}
}
}
int main()
{
prime(2022);
for(int i=0;i<primes.size();i++)
{
dp[i][2]=1;//初始化
}
for(int i=1;i<primes.size();i++)
{
for(int j=3;j<=2022;j++)
{
dp[i][j]=dp[i-1][j];
//不取第i个质数
if(j-primes[i]>=0)//若可以取第i个质数
{
dp[i][j]=max(dp[i][j],dp[i-1][j-primes[i]]+1);
}
}
}
cout<<dp[primes.size()-1][2022];
}
原题链接:I-时空的交织_2024牛客寒假算法基础集训营6 (nowcoder.com)
题目解释:
求子矩形所有元素的和尽可能大
解题思路:
考察:思维 转换?
与矩阵有关又与和有关,一开始的想法是前缀和暴力求解的方法,但很难不遗漏完全求解,而且估计肯定会超时。
正确思路:
这里我们矩阵内的每个点的价值都是ai*aj,这里我们简单看一下如果选择行的l到r,列的u和v
【可以理解为矩形面积】
这样就转换成求出a和b的最大子段和,但是这里有负数的可能!所以我们还要求出最小字段和。
注意!答案有四种可能,不要忽略了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+19;
ll a[N];
ll b[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
ll maxa=-4e9,maxb=-4e9,mina=4e9,minb=4e9;
ll ans1=0,ans2=0;
for(int i=0;i<n;i++)
{
ans1+=a[i];
if(maxa<ans1) maxa=ans1;
if(ans1<0) ans1=0;
ans2+=a[i];
if(mina>ans2) mina=ans2;
if(ans2>0) ans2=0;
}
ans1=0;ans2=0;
for(int i=0;i<m;i++)
{
ans1+=b[i];
if(maxb<ans1) maxb=ans1;
if(ans1<0) ans1=0;
ans2+=b[i];
if(minb>ans2) minb=ans2;
if(ans2>0) ans2=0;
}
// cout<<minb;
ll maxx=max({mina*minb,mina*maxb,maxa*maxb,maxa*minb});
cout<<maxx;
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
求完全图最短路
解题思路:
考察:完全图 数学 排序 错因:超时
我的思路简单粗暴啊,一开始就是当成最短路处理了,库库一顿套公式。结果超时wa了。后面没做出来。emm……正确的思路是
公式变形啊啊啊!我偷懒了根本就没有变形。
去掉绝对值,可以看出实质两点权值是两个点的最大值的两倍。由于完全图的特点:每两个点之间都有连线。然后经过仔细思考,可以得出结论,任意两点之间的最短路即是两个点直接连线。证明嘛,不知道咋解释,就是因为一条边的权值是两个点中的最大值两倍。绕再多的路,只会使得贡献越来越大。所以要使得总贡献最小,首先把贡献最小的点给先连接起来。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
typedef pair<int,int> PII;
typedef long long ll;
ll g[N];
ll dist[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>g[i];
ll sum=0;
sort(g+1,g+1+n);
for(ll i=n,j=1;i>=1;i--,j++)
{
sum+=g[i]*2*(n-j);
}
cout<<2*sum<<endl;
}
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
(上面那题的附加题)求完全图最短路
解题思路:
考察:完全图 数学 排序
题目基本没变,唯一的区别就是,权值计算的公式的区别
公式化简后两点连线的权值是两个点最小值的两倍。此时两个点的最短路有两种情况。
一是两点之间直接连线
二是以最小点为中转点(不会再有别的点,这个想了很久)
于是核心是min(4*min,|ai+aj|-|ai-aj|);
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
typedef pair<int,int> PII;
typedef long long ll;
ll g[N];
ll dist[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>g[i];
ll sum=0;
int k=0;
sort(g+1,g+1+n);
ll m=g[1];
for(int i=1,k=n-1;i<=n;i++,k--)
{
sum+=min(4*m,2*g[i])*k;
}
cout<<2*sum<<endl;
}
}
暴力
原题链接:D-小红的因式分解_牛客周赛 Round 50 (nowcoder.com)
题目解释:
解题思路:
考察:暴力 数论
看到数学公式首先解数学公式,获得三个方程,但是要求四个未知数,当时的想法是,枚举一个数,剩下用公式求解,发现遇到二元一次方程然后我就卡那里了。
其实看数据范围在加上公式完全可以推出a1 a2 b1 b2的数据范围并不是很大,暴力求解就好,但能在循环外面优化的要先优化不然就超时啦
还有一种解法是根据求根公式来判断是否有解,这里我看了半天也不是很理解┭┮﹏┭┮
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int a,b,c;
cin>>a>>b>>c;
int f=0;
// a1a2=a a1b2+a2b1=b b1b2=c
for(int i=-1000;i<=1000;i++)
{
if(!i||a%i!=0) continue;
for(int j=-1000;j<=1000;j++)
{
if(!j) continue;
if(a%i!=0||c%j!=0)
continue;
int a2=a/i,b2=c/j;
if(i*b2+a2*j==b)
{
f=1;
cout<<i<<" "<<j<<" "<<a2<<" "<<b2<<endl;
}
if(f)break;
}
if(f)break;
}
if(!f) cout<<"NO"<<endl;
}
}
原题链接:P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
求某一时刻在同一水平线上最多的点数
解题思路:
考察:枚举
这题没有考虑到枚举,一是没有想到怎么枚举,二是不知道怎么保存有用的点位信息。
看了题解,
方法一:由于数据很少只有1000。故可以暴力枚举每个点位,然后对于每个点位,枚举剩下n-1个点位,可以到达同一水平线的最大个数。统计最大个数的方法是计算每两个点之间可以到达相同位置的时间,用桶记录k时刻可以到达的点的个数。时间的计算法是距离差除以速度差
方法二:其实和方法一类似,但是枚举的是时间,至于为啥枚举到400就可以了还没想通思考……
对于每个时间点,判断一下能到达同一水平线【x和y轴分开判断】有多少个点,用map存储映射
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
typedef pair<int,int> PII;
int x[N],y[N],v[N],d[N];
map<int,int> tx,ty;
int main()
{
int n; cin>>n;
for(int i=0;i<n;i++)
{
cin>>x[i]>>y[i]>>v[i];
char e; cin>>e;
if(e=='U') d[i]=0;
if(e=='D') d[i]=1;
if(e=='L') d[i]=2;
if(e=='R') d[i]=3;
tx[x[i]]++;
ty[y[i]]++;
}
int sum=0;
for(auto k : tx) sum=max(sum,k.second);
for(auto k : ty) sum=max(sum,k.second);
for(int i=1;i<=400;i++)
{
tx.clear();
ty.clear();
for(int j=0;j<n;j++)
{
if(d[j]==0)
{
tx[x[j]]++; ty[y[j]+i*v[j]]++;
}
if(d[j]==1)
{
tx[x[j]]++; ty[y[j]-i*v[j]]++;
}
if(d[j]==2)
{
tx[x[j]-i*v[j]]++; ty[y[j]]++;
}
if(d[j]==3)
{
tx[x[j]+i*v[j]]++; ty[y[j]]++;
}
}
for(auto k : tx) sum=max(sum,k.second);
for(auto k : ty) sum=max(sum,k.second);
}
cout<<sum;
}
原题链接:P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
a:求多少个满足条件的子序列
b:求区间内满足质数等于p*p*q*q的数的个数
解题思路:
考察:暴力 dp 质数筛
a:填空题直接暴力求解,其实这题最佳是dp 递推求解。
注意一下先求出目标字符串用数字转字符串函数 to_string(),救命我还想着用‘0’+i的方法构造,这个只适用于数字0-9的单个数字。
我们用dp【4】记录每个2 20 202 2023 的个数
然后递推。这里要注意一下逻辑关系。我们遍历整个字符串,遍历到2的时候 2的个数要增加,但注意202是从20递推而来的所以dp【2】+=dp【1】。
为什么是+=? 因为我们是边循环边更新,当循环到第i个字符的时候,dp【i】存的是当前遍历到i位置时,当前位置能构成的最大个数。所以我们需要累加不同位置的总数要使用+=来累积前面的。
b:这题一开始还想用分解质因数,因为以为只要是质因数能组成的只要唯一一种?
正解是线性筛法求素数,然后暴力求解。注意这里的数据范围。因为要求到233333333333所以我们简单运算一下最好要求出1e7范围内的素数。 暴力求解只需要两层循环就可以,【我总是在这里纠结耗费好多时间还想用map不会重复计算。以后要动脑筋思考一下不要把问题复杂化!
然后要开longlong 乘1ll是防止过程爆掉
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[4]={0};
int main()
{
string s="";
for(int i=1;i<=2023;i++)
s+=to_string(i);
for(int i=0;i<s.size();i++)
{
if(s[i]=='2')
{
dp[0]++;
dp[2]+=dp[1];
}
if(s[i]=='0')
dp[1]+=dp[0];
if(s[i]=='3')
dp[3]+=dp[2];
}
cout<<dp[3];
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10000000;
int prime[N], cnt=0; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void is_prime()
{
for(int i=2;i<=N;i++)
{
if(st[i]==0)
{
prime[cnt++]=i;
}
for(int j=0;prime[j]<=N/i;j++)
{
st[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}
int main()
{
is_prime();
ll sum=0;
for(int i=0;i<cnt;i++)
{
for(int j=i+1;j<cnt;j++)
{
ll p=1LL*prime[i]*prime[i];
ll q=1LL*prime[j]*prime[j];
if(1LL*p*q<2333)continue;
if(1LL*p*q>23333333333333) break;
sum++;
}
}
cout<<sum<<endl;
}
原题链接:C-红魔馆的馆主_牛客周赛 Round 37 (nowcoder.com)
题目解释:
添加一个最短数字串添到n的后面,使得n变为495的倍数
解题思路:
考察:暴力 取模
没想出来没想出来…一看到数据范围10e18,再看倍数,立马想到二进制的方向了。啊哈哈没想到完全不对! 10e18的数范围long long 够啦TAT!然后防止long long 直接简化数据就可以啦取模取模!取模! 我服啦 然后暴力就可以解决啦,尝试遍历1-100
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
// 111101111
ll n;
cin>>n;
if(n%495==0)
{
cout<<-1;
return 0;
}
n=n%495;
for(int i=0;i<10;i++)
{
if((n*10+i)%495==0)
{
cout<<i;
return 0;
}
}
for(int i=0;i<100;i++)
{
if(((n*100+i)%495)==0)
{
cout<<i;
return 0;
}
}
}
原题链接:F-显而易懂的小题目_湖南工业大学第十五届蓝桥杯省赛选拔赛 (nowcoder.com)
题目解释:
解题思路:
考察:暴力 错因:超时
直接暴力求解肯定超时,那我们换个思路!【虽然比赛的时候还是没想出来
一开始思考的是化简数学公式,去掉绝对值。但我想了半天不知道a[i]+a[j]与1000的大小关系,分类讨论也思考了好久都没有什么头绪。
正解是a[i]的数据范围很小只有0~1000,我们可以从这里着手,统计a[i]的个数,思考a[i]个数对答案的贡献关系。对于
(1)a[i]=a[j],abs(i + j - 1000) * f[a[i]]; f[1]=1,f[2]=1+2,f[3]=1+2+3,f[4]=1+2+3+4;
这里贡献关系,可以草稿画图找规律。或者用组合数的方法,但目前我还没掌握……hehehe
(2)a[i]!=a[j],abs(i+j-1000)*cnt[i]*cnt[j]; 注意!a[i]=a[j]和a[i]!=a[j]的贡献值不同
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int a[N];
int cnt[1010]={0};
int f[N];
int main()
{
int n;
cin>>n;
f[1]=1;
ll sum=0;
for(int i=1;i<=n;i++)
f[i]=f[i-1]+i;
for(int i=0;i<n;i++)
{
cin>>a[i];
cnt[a[i]]++;//统计个数
}
for(int i=0;i<=1000;i++)
{
for(int j=i;j<=1000;j++)
{
if(cnt[i]!=0&&cnt[j]!=0)
{
if(i==j)
{
sum+=1ll *abs(i+j-1000)*f[cnt[i]];
}else
{
sum+=1ll *abs(i+j-1000)*cnt[i]*cnt[j];
}
}
}
}
cout<<sum;
}
原题链接:C-心绪的解剖_2024牛客寒假算法基础集训营6 (nowcoder.com)
题目解释:
n分解为三个斐波那契数之和
解题思路:
考察:暴力 打表
遇事不决先打表
注意题目的数据范围1<=n<=1e9。我们可以先打表,观察得知对于斐波那契数列,大约只在47左右数据就爆int了,所以只需要列举所有数据范围内斐波那契数列,用map<int,vector>映射保存即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
map<int,vector<int> > mp;
int main()
{
a[0]=0;
a[1]=1;
for(int i=2;i<100000;i++)
a[i]=a[i-1]+a[i-2];
for(int i=0;i<48;i++)
{
for(int j=0;j<48;j++)
{
for(int k=0;k<48;k++)
{
mp[a[i]+a[j]+a[k]].push_back(a[i]);
mp[a[i]+a[j]+a[k]].push_back(a[j]);
mp[a[i]+a[j]+a[k]].push_back(a[k]);
}
}
}
int q;
cin>>q;
while(q--)
{
int n;
cin>>n;
if(mp[n].size()!=0)
{
cout<<mp[n][0]<<" "<<mp[n][1]<<" "<<mp[n][2]<<endl;
}else cout<<-1<<endl;
}
}
前缀和
原题链接:D-异或炸弹(easy)_牛客小白月赛95 (nowcoder.com)
题目解释:
爆炸中心的 曼哈顿距离 小于等于 r 时,该位置就会收到爆炸的影响, 爆炸的影响就是给这个位置上的数异或 1.
文文给你这 m个炸弹的爆炸位置和爆炸半径,你需要回答文文这个矩阵中 1 的个数
解题思路:
考察:前缀和 异或和 错因:超时
这题题目主要考察前缀和和异或和的性质,写题的时候分析了一下爆炸范围是曼哈顿距离,形成的形状是菱形,就没有考虑用前缀和。没有想到可以逐行差分最后计算前缀和!,每次在爆炸范围内都会异或1,根据异或性质可得到,当异或是奇数的时候,累计为0,异或次数为偶数的时候累计次数为1。
对于这题我们先计算出爆炸的横坐标的范围,然后对于每一个横坐标,都利用曼哈顿距离判断一下,当前y的范围。然后在端点ymin异或和ymax+1异或。【至于为什么这样,应该适合异或和的性质有关,目前还不是很了解】。
只要在最后统计的时候,对于每行都异或和一下,只要为1代表当前格子最后为1.【原理也还不是很明白】
画图模拟了一下 例如010001 异或完变成 011110 010101001 -->011001110 是符合题目要求的
#include<bits/stdc++.h>
using namespace std;
const int N=3e3+10;
int c[N][N]={0};
int n,m;
void bao(int x,int y,int r)
{
int xmin=max(1,x-r); int xmax=min(n,x+r);
for(int i=xmin;i<=xmax;i++)
{
int d=r-abs(x-i);
int ymin=max(1,y-d);
int ymax=min(n,y+d);
c[i][ymin]^=1; c[i][ymax+1]^=1;
}
}
int main()
{
cin>>n>>m;
while(m--)
{
int x,y,r;
cin>>x>>y>>r;
bao(x,y,r);
}
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
c[i][j]^=c[i][j-1];
ans+=c[i][j];
}
}
cout<<ans;
}
原题链接:P8700 [蓝桥杯 2019 国 B] 解谜游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)D-因子区间_牛客周赛 Round 44 (nowcoder.com)P8700 [蓝桥杯 2019 国 B] 解谜游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:求区间符合条件的数对:因数相等的数对个数
解题思路:
考察:前缀和 离散化 预处理 错因:超时
这题的题目可以说是很眼熟很眼熟了一眼就是考察前缀和的,但是还是不知道从何下手。暴力求解超时了。
我的比赛思路是求出每个数的因数个数,思考相同的因素个数在区间内的贡献。总体思路的是对了,因数的个数贡献是每个不同因数的相同因数的个数-1的阶和(可以这么说吗…画图得出来的)
但比赛的时候在这里卡主了TAT 原因是前缀和需要维护每一个位置和所有因数。这样空间就开的太大了,但是没有想到合理的解决方法。主要是没有想到1e5的数据因子的个数居然只有128这么小。
那么我们就可以用二维数组ans【i】【j】记录每个因数个数i的前缀和(这里超级不熟练,码代码码了很久)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int a[N];
int d[N];
int ans[130][N]={0};
ll c[N];
int divide(int x)
{
int cnt=0;
for(int i=1;i<=x/i;i++)
{
if(x%i==0)
{
cnt++;
if(x/i!=i) cnt++;
}
}
return cnt;
}
int main()
{
int n,q;
for(int i=1;i<=1e5;i++)
{
c[i]+=c[i-1]+i-1;
}
cin>>n>>q;
map<int,int> mp;
map<int,int> d;
vector<int> b;
for(int i=1;i<=1e5;i++)
{
mp[divide(i)]++;
d[i]=divide(i);
}
for(int i=1;i<=1e5;i++)
{
if(mp[i])
{
b.push_back(i);
}
}
sort(b.begin(),b.end());//用来做离散化二分的映射?
for(int i=1;i<=n;i++)
{
cin>>a[i];
for(int j=1;j<130;j++)
{
ans[j][i]=ans[j][i-1];
}
ans[d[a[i]]][i]=ans[d[a[i]]][i-1];
ans[d[a[i]]][i]++;
}
while(q--)
{
int l,r;
cin>>l>>r;
ll sum=0;
for(int i=1;i<=128;i++)
{
int cnt=ans[i][r]-ans[i][l-1];
sum+=c[cnt];
}
cout<<sum<<endl;
}
}
贪心
题目解释:
给a、b两个序列,要从n+1的位置交换到位置m,对于我的位置i,要交换到位置j(j<k<i),需给j a[j]个硬币,并且对于中间的每个位置k还需要b[i]个硬币。
解题思路:
考察:贪心 前缀和 错因:考虑不周
思考对于每个位置m贡献的最小硬币的路径都是一样的,这个很简单,都是取min(a[i],a[j]),然后累加和就可以实现,但是这样漏掉了一种可以回头的情况。可以证明从n+1的位置到m是不存在回头的情况的,因为在前面回头的贡献只会不变或者增加。但是可以设想这一种情况,a【m】的贡献过于大,超过了a【1】~a【m-1】的贡献值,那么就存在着回头的情况,【其实我觉得这里题目没有说清楚,只说明我可以与前面交换位置,但是没说和后面交换位置然后获得a【i】个硬币呀。。所以当时只考虑到花费的,就完全没想到获得的了】这里通过前缀和求出每段的最小花费,然后暴力枚举1-m回头的所有情况,注意可以证明最小花费是回头直接跳到m点,其他的跳法贡献只会更大,最后判断最小值即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int a[N];
int b[N];
ll s[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>b[i];
ll sum=0;
for(int i=1;i<=n;i++)
{
a[i]<=b[i]?sum+=a[i]:sum+=b[i];
s[i]=sum;
}
s[n+1]=s[n];
ll ans=s[n]-s[m]+a[m];
for(int i=1;i<=m;i++)
{
ans=min(ans,s[n]-s[i]+a[i]);
}
cout<<ans<<endl;
}
}
思维
原题链接:P10416 [蓝桥杯 2023 国 A] XYZ - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
解题思路:
考察:找规律
哇,万万没想到这题是找规律哇,题解写得很详细如下
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll sum=0;
int main()
{
int t;
cin>>t;
while(t--)
{
ll l,r;
cin>>l>>r;
if(r<2*l)
{
cout<<0<<endl;
continue;
}
ll ans=1ll*(r-2*l+1)*(r-2*l+2)/2;
cout<<ans<<endl;
}
}
原题链接:P8807 [蓝桥杯 2022 国 C] 取模 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
解题思路:
考察:枚举,取模,鸽巢原理
同样的数据范围1e9,暴力会超时,打表观察数据,多思考一下条件不成立的情况。我们可以发现,当条件不成立的时候,即n%1到n%m没有一个相同的余数。逐个分析一下,%1的可能只有0,%2的可能有0,1,%3的可能有0,1,2。因为%1的可能只有0,要想条件不成立%2的结果必须为1,%3的结果必须为2.以此类推,当条件不成立的时候,我们遍历1-m,n%i的结果都为i-1。
注意一下当m>=n的时候必定存在相同的余数n%1=0 n%n=0,但是1是特殊情况所以综合考虑m>n+1条件必定成立
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
if(m>n+1)
{
cout<<"Yes"<<endl;
continue;
}
int f=0;
for(int i=1;i<=m;i++)
{
if(n%i!=i-1)
{
f=1;break;
}
}
if(f) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
原题链接:P8700 [蓝桥杯 2019 国 B] 解谜游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:有三种操作数,1.可以三个圈可以同时向左转 2.三个圈可以同时向右转 3.在位置0的地方三个圈可以交换位置 问可不可以把外圈变为12个绿,中圈变为8个红,内圈变为4个黄
解题思路:
考察:倍数
这里是真想不到呀,应该从特殊情况考虑,注意这里都是同时转,也就是相对位置基本不会怎么改变,就算内外圈就换位置也不会改变【这里我还推了很久的公式…结果没有推出答案白费功夫】,注意三个圈塑料棒的个数分别是4,8,12,恰好成倍数关系。题目给出的三个操作只有操作三有用,第1,2都是迷惑性操作,其实对答案没有帮助【我还是研究了很久……】,
我们可以从位置0 的地方考虑
通过观察发现,无论怎么转只会与对应位置地方交换。
就是这里的相同数字可以交换。
所以根据这个划分为三个组,只要每组满足特定条件,即可交换到特定的位置。
#include<bits/stdc++.h>
using namespace std;
int cnt[200]={0};
int main()
{
int t;
cin>>t;
while(t--)
{
string a,b,c;
cin>>a>>b>>c;
memset(cnt,0,sizeof(cnt));
int f=1;
for(int i=0;i<4;i++)
{
memset(cnt,0,sizeof(cnt));
cnt[c[i]]++;
cnt[b[i]]++; cnt[b[i+4]]++;
cnt[a[i]]++; cnt[a[i+4]]++; cnt[a[i+8]]++;
if(cnt['Y']!=1||cnt['R']!=2||cnt['G']!=3)
{
f=0;
break;
}
}
if(f) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
原题链接:问题 - B - Codeforces
题目解释:
解题思路:
考察:二进制搜索,贪心,数据结构
错的原因是:漏掉情况考虑啦
因为第一场比赛从最前面两个人开始,之后的每一场比赛都是上一场获胜的和接下来的人比较。所以当轮到k比赛时,和他比较的一定是k之前最大的数。所以只要前面的人有比他大的数,那么k获胜的场数就为0.所以我们需要交换k与前面最大的数的位置,然后计算获胜的场数。
当时写题的时候是分成两种情况考虑,k前面有比k大的数和k前面没有比k大的数两种情况。
事实证明,这样的分法把问题复杂化了,而且漏掉一种情况。例如:1 2 3 9 7【k】
这种情况虽然k前面有比他大的数9,但是交换后胜利的场数仅为1场,而把它和1交换则可以达到最佳。
所以正解是求出第k个人和第一个人交换和第k个人和第一个比它大的人交换的方案取更优
注意:当换到第一个位置时,是第一场比赛比较特殊。善于利用swap简化思维过程。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
swap(a[k],a[1]);
int sum=0;
for(int i=2;i<=n;i++)
{
if(a[i]<a[1])
sum++;
else break;
}
swap(a[k],a[1]);
int x=-1;
for(int i=1;i<k;i++)
if(a[i]>a[k])
{
x=i;break;
}
int ans=0;
if(x==-1)
{
ans=k-1;
for(int i=k+1;i<=n;i++)
{
if(a[i]>a[k])
{
x=i;break;
}
}
ans+=(x-k-1);
}else
{
int q=-1;
for(int i=x+1;i<k;i++)
{
if(a[i]>a[k])
{
q=i; break;
}
}
if(x!=1)
ans=1;
if(q!=-1)
ans+=(q-x-1);
else ans+=(k-x-1);
}
cout<<max(sum,ans)<<endl;
}
}
题目解释:
给你n个小木棍,拼成0-9,问能拼成的最大数和为。
解题思路:
考察:思维
先打表观察数据怎么来的。然后观察出规律,当木棍足够3个就可以拼成最优解7,剩下的余数在0-2之间,但只要2个木棒可以拼成数字,所以只要剩下2个木棒就可以拼。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<int,int> mp;
int main()
{
int t; cin>>t;
mp[1]=0;
mp[2]=1;
mp[3]=7;
while(t--)
{
ll n; cin>>n;
ll sum=0;
sum=7*(n/3)+(n%3)/2;
cout<<sum<<endl;
}
return 0;
}
原题链接:D-迷途之家的大贤者_牛客周赛 Round 37 (nowcoder.com)
题目解释:
小红采用最优策略删除子串,使剩下来的字符串字典序尽可能大,小紫采用最优策略删除子串,使得剩下来的字符串字典序尽可能小。小红先手。
解题思路:
考察:思维 博弈
正解是最后一定剩下一个字符:首字符或者尾字符其中最大一个。
证明:
首先,由于其中一个人目的是字典序最小,所以最后字符串肯定只剩下一个字母。先手一定可以通过一次删到只剩首或者尾,就结束游戏。所以答案的下界一定能取到,首尾中较大的字母。
然后假设中间有更大的字母,先手得删两次才能只剩它,但是后手可以删掉它,所以先手取不到中间那个更大的字母。(差不多这个意思),于是直接猜一下这个结论就过了。【抱好一丝 没猜到
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
string s;
cin>>s;
if(s[0]>=s[n-1])cout<<s[0];
else cout<<s[n-1];
return 0;
}
位运算
原题链接:2.空间复杂度【算法赛】 - 蓝桥云课 (lanqiao.cn)
题目解释:
给一个内存大小,可以转换为多长的内存为b的数组
解题思路:
考察:存储单位
这里很简单就是记住单位就好
1kb=1024b
1Mb=1024*1024b
1MB=1024kb
原题链接:B-小红的小红矩阵构造_牛客周赛 Round 36 (nowcoder.com)
题目解释:
判断n行m列的矩阵,是否满足所有元素之和恰好等于x,且每行、每列的异或和全部相等。
解题思路:
考察:异或
这题还是很简单的,但是有几个知识点不熟悉要记一下
异或:∧ 运算规则相同为0,不同为1
异或和:将所有的数异或。
任何数和0异或为本身
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[110][110];
int main()
{
ll n,m,x;
cin>>n>>m>>x;
ll sum=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>a[i][j];
sum+=a[i][j];
}
}
if(sum!=x)
{
cout<<"wrong answer"<<endl;
return 0;
}
int k=0;
for(int i=0;i<m;i++) k^=a[0][i];
for(int i=0;i<n;i++)
{
int ans1=0;
for(int j=0;j<m;j++)
{
ans1^=a[i][j];
}
if(ans1!=k)
{
cout<<"wrong answer"<<endl;
return 0;
}
}
for(int j=0;j<m;j++)
{
int ans2=0;
for(int i=0;i<n;i++)
{
ans2^=a[i][j];
}
if(ans2!=k)
{
cout<<"wrong answer"<<endl;
return 0;
}
}
cout<<"accepted";
}
构造
原题链接:C-小红的字符串构造_牛客周赛 Round 38 (nowcoder.com)
题目解释:
构造一个长度为n,有k个回文子串【长度>2】的字符串
解题思路:
考察:构造
这题就是很简单呀,原谅我又想复杂了,第二眼就注意到了题目要求构造k个回文子串的数据特殊性:0<=k<=n/2,但是我还是没想出来我服啦,错的原因是一直在找刚好能构造一半的特殊回文字符串的规律,然而找了好久没找出来。
答案很简单因为k只为n的一半,构造一个长度>2的回文子串可以是aa或bb这样就可以啦,因k不会大于n/2,所以只要aabbccdd这样构造一定能满足k的要求! 所以构造一个回文子串每次加上相同的两个字符即可,剩下就abcd轮流就好。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,k;
cin>>n>>k;
string s="";
int j=0;
for(int i=0;i<n;)
{
char x=(j)%26+'a';
j++;
if(k)
{
s+=x; s+=x;
i++; i++;
k--;
}else{
s+=x;
i++;
}
}
cout<<s<<endl;
}
原题链接:E-小红的小红走矩阵_牛客周赛 Round 36 (nowcoder.com)
题目解释:
按照题目要求构造图
解题思路:
考察:构造
这里可以观察一下数据特点。可以观察到构造出的路径要带一拐弯~。
rand():rand产生一个0-0x7fff的随机数,即最大是32767的一个数
写的时候想复杂了,没想到还可以这样? 所以构造一个弯出来,剩下的就都可以用随机数解决了
#include<bits/stdc++.h>
using namespace std;
char a[1010][1010];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
a[i][j]=rand()%26+'a';
}
}
a[0][0]='a'; a[0][1]='b'; a[0][2]='b';
a[1][0]='a';a[1][1]='c'; a[1][2]='c';
a[2][0]='b'; a[2][1]='c';
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cout<<a[i][j];
}
cout<<endl;
}
}
原题链接:E-小红的无向图构造_牛客周赛 Round 35 (nowcoder.com)
题目解释:
构造一个无向连通图,满足共有nnn个点mmm条边,且iii号节点到 1 号节点的最短路长度恰好为aia_iai。
解题思路:
考察:构造 图
这里用到了几个性质
构成连通图,所需要的最小边数是(n-1)条边,即构成树。
所以当m<(n-1)时,不构成连通图。我们可以记录每个点的最短路,先每个点连到上一层最短路的第一个点,先构造一棵树。注意当中间最少点,也不成立。
然后构造第一种情况:可以将同层次的点连接,不影响最短路。
第二种情况:可以将最短路相差1的点连接,也不影响最短路。
最后判断一下能否成功连成m条边
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
vector<PII> an;
vector<int> a[N];//a[i]表示最短路为i的点
int main()
{
int n,m;
int num=0;
cin>>n>>m;
int big=0;
for(int i=1;i<=n;i++)
{
int x; cin>>x;
big=max(big,x);
a[x].push_back(i);
if(x==0&&i!=1)
{
cout<<-1; return 0;
}
}
if(m<n-1)
{
cout<<-1; return 0; //不构成连通图
}
for(int i=1;i<=big;i++)
{
for(int j=0;j<a[i].size();j++)
{
if(a[i-1].size()==0)
{
cout<<-1; return 0;//无法连接成最短路
}else
{
an.push_back({a[i][j],a[i-1][0]}); num++;
}
}
}
// 把最短路相同的点相连
if(num<m)
for(int i=1;i<=big;i++)
{
for(int j=0;j<a[i].size();j++)
{
for(int k=j+1;k<a[i].size();k++)
{
an.push_back({a[i][j],a[i][k]}); num++;
if(num==m)
break;
}
if(num==m)
break;
}
if(num==m)
break;
}
//把最短路相差1的点相连
if(num<m)
for(int i=1;i<big;i++)
{
for(int j=1;j<a[i].size();j++)
{
for(int k=0;k<a[i+1].size();k++)
{
an.push_back({a[i][j],a[i+1][k]});
num++;
if(num==m)
break;
}
if(num==m)
break;
}
if(num==m)
break;
}
if(num==m)
for(auto [x,y]: an)
{
cout<<x<<" "<<y<<endl;
}
else cout<<-1;
}
二分
原题链接:P8800 [蓝桥杯 2022 国 B] 卡牌 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
转换一下题目大意是,给一个数组和一个数字m,m可以分配给每个数组,并且分配数量不能大于b【i】的限制,问a中最小值的最大值是多少
解题思路:
考察: 二分 错因:超时
一开始我的想法是把m一个一个分配,用优先队列来实现每次给最小的分配就行,但是没有注意到m的数据范围n^2【这个提示很明显了,肯定会超时,但是还是没有注意到】,然后考虑二分
注意,以后输出只有一个,并且有超时的可能,大概率用二分优化
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int a[N];
int b[N];
ll n,m;
int check(int x)
{
// cout<<x<<endl;
ll ans=0;
for(int i=1;i<=n;i++)
{
if(x-a[i]>b[i]) return 0;
ans+=max(x-a[i],0);
}
if(ans>m) return 0;
else return 1;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>b[i];
int l=0,r=1e9;
while(l<r)
{
int mid=l+r+1>>1;
// cout<<mid<<endl;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l;
}
原题链接:问题 - E - 代码强制 (codeforces.com)
题目解释:
没有排序的二分,最多交换两次使得最后二分找到的是x
解题思路:
考察:构造 二分
参考链接:Codeforces Round 935 (Div. 3) A-H - 知乎 (zhihu.com)
看完直呼好牛哇~
模拟二分的过程
x=1时特判,直接把1换到前面就行
x!=1时
假如最后到了 L
- p[L]==x:不用换,直接可以
- p[L]<x:x实际和p[L]的效果是一样的,交换p[l]和x的位置即可
- p[L]>x:L 如果转移了必定满足p[L]<=x,所以L一次都没转移,L=1,二分的位置m都满足p[m]>x,发现二分的位置没有包含值x,注意第一个位置一定不会用作m。因此第一个位置值是多少 以及 值x在哪 是不影响二分的过程的,只要把x放到第一个位置即可。
#include<bits/stdc++.h> using namespace std; const int N=2e5+10; int a[N];int x,n; int check(int m) { if(a[m]<=x) return 1; else return 0; } int main() { int t; cin>>t; while(t--) { cin>>n>>x; int st; for(int i=1;i<=n;i++) { cin>>a[i]; if(a[i]==x) st=i; } int l=1,r=n+1; while(r-l>1) { int mid=r+l>>1; if(check(mid)) l=mid; else r=mid; } if(a[l]==x) cout<<0<<endl; else if(a[l]<x) { cout<<1<<endl; cout<<l<<" "<<st<<endl; } else { cout<<1<<endl; cout<<l<<" "<<st<<endl; } } }
原题链接:B-爱恨的纠葛_2024牛客寒假算法基础集训营6 (nowcoder.com)
题目解释:
重新排序a数组,求最小的i∈[1,n],∣ai−bi∣。
解题思路:
考察:排序,二分
看了一下别人的题解基本上都是用二分查找,将a数组升序排序后,用二分查找亲密度最小值,
注意:对于b数组的每个数,都要进行两边查找。一遍是查找a[i]>=b[k],一遍是a[i]<=b[k]的临界值,找出亲密度最小的一边查找一边进行交换。
这里我用了另一种方法。
把a数组与b数组的数都存入c数组中【注意同时要标记下标,这里用PII存,b数组的下标+N表示与a数组区分。将数组c从小到大排序后遍历。亲密度最小的一定存在相邻的c中,因此遍历数组c,更新状态即可。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N= 10000000;
vector<PII> c;
int a[N];
int b[N];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
for(int i=0;i<n;i++)
{
c.push_back({a[i], i});
c.push_back({b[i], i + N});
}
sort(c.begin(),c.end());
int sum=a[0]-b[0];
int xa,xb;
for(int i=0;i<c.size()-1;i++)
{
if(abs(c[i].second-c[i+1].second)>N)
{
if(sum>abs(c[i].first-c[i+1].first))
{
int aa=min(c[i].second,c[i+1].second);
int bb=max(c[i].second,c[i+1].second);
bb-=N;
sum=abs(c[i].first-c[i+1].first);
swap(a[aa],a[bb]);
}
}
}
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
}
并查集
原题链接:E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)
题目解释:
求一共有多少株合根植物
解题思路:
考察:并查集 错因:
想了dfs就是没有想到是并查集,现在还都还不太理解题目的意思,样例的答案咋来的为啥要用并查集
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int f[N];
int find(int x)
{
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void merge(int a,int b)
{
int fa=find(a);
int fb=find(b);
f[fa]=fb;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n*m;i++) f[i]=i;
int k; cin>>k;
while(k--)
{
int a,b;
cin>>a>>b;
merge(a,b);
}
int sum=0;
for(int i=1;i<=n*m;i++)
{
if(i==f[i]) sum++;
// cout<<f[i]<<endl;
}
cout<<sum;
}
双指针
原题链接:E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)D-小红的子数组排列判断_“现代汽车中国前瞻软件赛杯” 牛客周赛 Round 43 (nowcoder.com)E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)
题目解释:
求多少个连续子序列可以构成排列
解题思路:
考察:双指针 错因:超时
其实这题思路比赛是想到了,但是看题目的数据范围a[i]是可以大于k的然后就不知道该咋办了,但没想到赛后看题解好像大家都是这样写的,是数据太水了吗思考
其实这题考察双指针,对于每个点i我们计算可不可维护一个区间:区间长度为k并且区间每个点都只出现一次。符合条件就累加答案。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int cnt[N]={0};
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int sum=0;
for(int i=1,j=1;i<=n;i++)
{
cnt[a[i]]++;
while(cnt[a[j]]>1)
{
cnt[a[j]]--;
j++;
}
if(i-j+1==k)
{
sum++;
}
}
cout<<sum<<endl;
}
原题链接:E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)
题目解释:
求一个数组中可以最大构成多少个等比数列
解题思路:
考察:双指针、分解因数、排序 错因:超时
一开始就排序暴力求解wa了,比赛完又思考了一下,因为是等比数列,对于构成等比数列 的每个元素都有相同的因数,就想到分解因素求解,但是分解完就不知道怎么选了,当时想的是把有相同因素的数组放在一个集合里,然后排序去重判断个数啥的,但是这样不能保证一定构成等比数列,如果再加判断那肯定又会超时,而且对于1这个特殊的数也不能放进去。
后来看了正解是双指针+分解因数。先预处理将每个元素的个数用cnt【i】记录一下。为了方便后续判断和公比为1的特殊情况的处理。并且将数组重新排序后,对于每个元素,都作为等比数列的最后一项,然后对其分解因数,在分解因数的过程中判断该等比数列是否能往前拓展,并更新记录等比数列的最大值。
妙呀妙呀 我趣两个结合我怎么没有想到!
订正的时候老是错一个地方就是分解质因数一定要从1开始,虽然后面判断的时候筛掉了等比为1的等比数列,但是对于本身这个因素不然就可能会漏掉,例如q=4时的4 1。就会漏掉这种情况
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int a[N];
int cnt[N]={0};
int main()
{
int n; cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
cnt[a[i]]++;
}
int sum=0;
sort(a,a+n);
for(int i=0;i<n;i++)
{
sum=max(sum,cnt[a[i]]);
for(int k=1;k<=a[i]/k;k++)
{
int x=a[i];
if(x%k==0)
{
int ans=1;
while(x%k==0&&cnt[x/k]&&k>1)
{
ans++; x/=k;
}
sum=max(sum,ans);
x=a[i];
if(x/k!=k)
{
int ans=1;
while(x%(a[i]/k)==0&&cnt[x/(a[i]/k)]&&(a[i]/k)>1)
{
ans++; x/=(a[i]/k);
}
sum=max(sum,ans);
}
}
}
}
cout<<sum;
}
题目解释:
给出两个长度分别为n,m的数组a,c。从c中选择n个数并且重新排列,使得尽可能大,求D的值。
解题思路:
考察:双指针、贪心、排序 错因:超时
一开始的思路是用next_permutation函数,求出b数组所有排列的组合暴力求解,但是样例三超时了。
然后考虑了一下贪心的方法,思考了很久,发现规律好像是将a数组重新排列之后从两端寻找局部最优策略。即最小的数匹配现存最大的,最大的数匹配现存最小的。就是写出了下面的错误题解
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
sort(a,a+n);//排序
sort(b,b+m,cmp);
ll sum=0;
for(int i=0,l=0,r=m-1,j=n-1;i<=j;l++,r--,i++,j--)
{
// cout<<i<<" "<<j<<" "<<l<<" "<<r<<endl;
if(i!=j)//每一次操作选两个,从两端开始选 差值最大的两个
{
sum+=abs(a[i]-b[l]);
sum+=abs(a[j]-b[r]);
}else{
sum+=max(abs(a[i]-b[l]),abs(a[j]-b[r]));
}
if(i==j) break;
}
cout<<sum<<endl;
}
}
wa了样例二,错在最优策略不一定是对于a数组先匹配最小的数然后就匹配最大的数。
正确思路:局部最优策略是比较a数组的最小数匹配b的现存最大数和a最大数匹配b的现存最小数,然后从中选择最优的一项。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int a[N];
int b[N];
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0;i<m;i++)
{
cin>>b[i];
}
sort(a,a+n);
sort(b,b+m);
ll sum=0;
int i=0,j=n-1;
int l=0,r=m-1;
while(i<=j)
{
if(abs(a[i]-b[r])>=abs(a[j]-b[l]))
{
sum+=abs(a[i]-b[r]);
i++;r--;
}else{
sum+=abs(a[j]-b[l]);
j--;l++;
}
}
cout<<sum<<endl;
}
}
数论
原题链接:B-小红的约数_牛客练习赛127 (nowcoder.com)
题目解释:
解题思路:
考察:数论 快速幂 逆元
这题感觉不是我能力范围之类可以解决的题目呀,我简单记录一下解题思路好了。这题的核心方法主要用到了快速幂和求逆元。重点在于公式的推导。注意这里的所有约数都可以用质数来表示。分别是质数p^(0-ai).然后根据这个把结果的表达式求出来,后来利用等比数列求和公式化简,最后化成乘积和逆元的形式。
原题链接:C-两个函数_牛客小白月赛98 (nowcoder.com)
题目解释:
解题思路:
考察:数论 错因:调用的递归层数过多
这里一开始用递归函数求解然后wa拉,一看x的范围1e9,后来开始尝试吧函数的结果存起来但是发现会内存超限,然后突然发现数据公示可以带进去化简,结果成功解的正确答案是a*(n-1)!,但是n的范围1e9,思考然后我想了无数种方法都求不出结果,最后看题解,救命等差数列求和公式我怎么忘记啦!还要注意分开取模,不然会爆ll
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=998244353;ll a;
const int N=1e7+10;
ll g[N]={0};
int main()
{
int q;
cin>>q;
g[0]=0;
ll sum=0;
while(q--)
{
int x;
cin>>a>>x;
if(x==1)cout<<a%M<<endl;
else {
int ans1=1ll*a*a%M;
int ans2=1ll*x*(x-1)/2%M;
cout<<1ll*ans1*ans2%M<<endl;
}
}
}
原题链接:P8799 [蓝桥杯 2022 国 B] 齿轮 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:给若干个已知半径的齿轮大小,连接在一起旋转跳跃,可以移动顺序,问是否存在第一个和最后一个齿轮角速度大小正好成k倍
解题思路:
考察:数论 错因:超时
角速度和线速度公式:线速度=角速度*R
这题还是很容易想到的,其实思路就是问数组中是否存在两个数呈k倍。
主要错误是超时了,一开始的写法是在循环内判断,然后改成预处理每个数,判断每种倍数(1-N)可能存不存在,wa
然后尝试改成对于每个数,每次内存循环+a[i],然后判断是否存在,但是还是wa了一个数据点,
最后加上相同的跳过才完全正确。
这里容易错误的点还有一个是倍数为1的特判
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
int f[N]={0};
int b[N]={0};
int main()
{
int n,q;
cin>>n>>q;
int f_2=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
f[a[i]]++;
if(f[a[i]]>=2) f_2=1;
}
//转化为查询数组倍数关系是否存在
sort(a,a+n);
for(int i=0;i<n;i++)
{
if(a[i]==a[i+1]) continue;
for(int j=a[i];j<=a[n-1];j+=a[i])
{
if(f[j])
{
b[j/a[i]]=1;
}
}
}
while(q--)
{
int x;
cin>>x;
if(x==1)
{
if(f_2) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
continue;
}
if(b[x]) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
原题链接:C-找到数字_牛客练习赛125 (nowcoder.com)
题目解释:
解题思路:
考察:枚举 公式变形 思维
比赛的思路其实很接近题解啦,但是被卡住了没往下想。
这题实际上求的是x去掉最高位+x去掉最低位的和=y有多少种情况。
我们看数据范围y1e17 暴力枚举肯定会超时。然后观察相加的两部分有什么联系,我当时比赛的时候观察到相差的两部分很像 其实可以分为ab+bc 中间一段是相同的 也就是单独枚举a b c就可以啦
而 a c是个位数,范围是1-9和0-9 但是b枚举起来范围就很大,我当时判断一下肯定会超时就没有继续往下想。但其实!我们不用把所有b都求出来,因为y已知而a c枚举的范围又非常小,我们可以利用一下已知两边之和求第三边的思想,来求b,判断b是否符合条件即可。然后这里就要用到公式的转换
x=a*10^(n-1)+b+c
f【x】=y=[ a*10^(n-2)+b] + [ b*10+c ]= a*10^(n-2)+b*11+c
11*b=y-a*10^(n-2)-c
然后根据数据范围把所有符合条件的枚举并筛选出来,注意这里判断b的时候我们b
原题链接:P8649 [蓝桥杯 2017 省 B] k 倍区间 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:求此序列中有多少子序列和为 k 的倍数。
解题思路:
考察:前缀和 取模 同余
一开始就用取模前缀和然后枚举符合条件的个数,感觉思路很接近啦。这里主要是漏掉了一个知识点。
同余公式:a%k=b%k那么|a-b|%k=0
这题的本质是:区间和%k=0的个数,也就是ans【j】-ans【i-1】%k=0的个数,于是转化成多少对前缀和同余的个数。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int ans[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int sum=0;
// ans[1]=1;
for(int i=0;i<=n;i++)
{
sum+=ans[a[i]%k];
ans[a[i]%k]++;
}
cout<<sum;
}
原题链接:P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)P9231 [蓝桥杯 2023 省 A] 平方差 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:计算区间内有多少个数满足x=i^2-j^2
解题思路:
考察:数学
一开始以为是dp,简直大错特错TAT.然后醒悟一点发现这个等式是一个完全平方公式。
x=a^2-b^2=(a+b)(a-b) 然后可以推出a>b,a+b<=x,a b<x【注意这里是乘法】
出题大大永远高我一层呀。我就没往下想了。其实是可以继续推导的!我们令x先存在,然后推导出x存在的条件。假设等式成立,我们令ans1=(a+b),ans2=(a-b)【这样等价代还是为了略去加减法?方便后续连乘可以分解因数】
于是这里可以解!方!程!组! _(¦3」∠)_ 求解可得2a=ans1+ans2.我们这里不是判断a而是ans1!ans2!。说明ans1+ans2 一定为偶数。所以ans1与ans2一定是两个偶数或者两个奇数。
再根据分解质因数的思想。这里一定是 可分解出2个及以上的2或者一个没有。更简单的说x可以被4整除或者x为奇数,则条件成立
#include<bits/stdc++.h>
using namespace std;
const int N=4e4 ;
int a[N]={0};
int divide(int x)
{
if(x&1) return 1;
int s=0;
while(x%2==0)
{
s++;
x/=2;
}
if(s>1) return 1;
return 0;
}
int main()
{
int l,r;
cin>>l>>r;
int sum=0;
for(int i=l;i<=r;i++)
{
if(i%2!=0||i%4==0)
sum++;
}
cout<<sum;
}
原题链接:P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
解题思路:
考察:数学
这就是数学不好嘛 写的很清楚了还是不理解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N];
int main()
{
int n;
cin>>n;
int da=1e9,xi=0;
while(n--)
{
int a,b;
cin>>a>>b;
xi=max(xi,(int)(a/(b+1))+1);
da=min(da,(int)(a/b));
}
cout<<xi<<" "<<da;
}
原题链接:E-小红不想做莫比乌斯反演杜教筛求因子和的前缀和_牛客周赛 Round 39 (nowcoder.com)
题目解释:求有多少个符合条件的蛋糕种类 长宽高和面积符合条件
解题思路:
考察:数学 优化
错的原因是这里超时啦。当然我直接暴力求解三层循环。看到有面积有数学计算,就想到公式变形了,
但是居然没想到怎么去掉第三层循环。以后记住啦 已知那么多变量,未知变量可以用已知变量代替
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+10;
int main()
{
int n,m,p,x;
cin>>n>>m>>p>>x;
// ll x=n*m+2*n*p+2*m*p;
// x=nm+2p(n+m);
// p=(x-nm)/2(n+m);
int s=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(i*j>=x||(i+j)>=x)break;
int ans1=x-i*j;
int ans2=2*(i+j);
if(ans1%ans2!=0) continue;
int k=ans1/ans2;
if(k>=1&&k<=p) s++;
}
}
cout<<s<<endl;
}
原题链接:B-小红不想做鸽巢原理_牛客周赛 Round 39 (nowcoder.com)
题目解释:
解题思路:
考察:数学 取模
因为这里每次拿走k个,并且最后剩余的总数要小于k。所以剩余的总数一定是sum%k!
然后就很简单啦,脑子就是没转过来┭┮﹏┭┮
因为要求取剩余的种类最少,这里拿走的没有任何条件限制然后排序从最大的开始留下,计算个数就可以。
还是没想明白自己错哪了┭┮﹏┭┮ 我就每次拿走最少的 有问题嘛?┭┮﹏┭┮
还有题的a【i】为啥也可以取模,变成0 的话对答案没影响嘛 想不通想不通,突然想明白!因为这里只要保留种类最少的,只要剩下来k个,无论哪个先没有都没关系
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int a[N];
int main()
{
int n,k;
cin>>n>>k;
ll s=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
a[i]%=k;
s+=a[i];
}
sort(a,a+n);
s%=k;
int cnt=0;
int ans=0;
for(int i=n-1;i>=0;i--)
{
if(ans>=s)
break;
ans+=a[i]; cnt++;
}
cout<<cnt;
}
题目解释:判断n的阶乘的约数个数的和
解题思路:
考察:数学 分解质因数
这里主要考察了数学性质:约数的个数和等于每个约数个数加1的乘积。
注意这里求的是n的阶乘
原题链接:6.宝石的价值 - 蓝桥云课 (lanqiao.cn)
题目解释:查询区间范围内数的位数总和
解题思路:
考察:数学、前缀和
错的原因超时
这里有几个知识点!
这里其实如果想到了还是不难的。先求出左右端点的位数。然后利用前缀和的思想求解。这里没有解出来的原因是没有考虑到分断求解的方法例如10-99的贡献值都是2。也没有想到前缀和的思想。然后对于端点的求值还是很头疼,
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int get(int x)
{
int ans=0;
while(x)
{
ans++;
x/=10;
}
return ans;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int l,r;
cin>>l>>r;
int tl=get(l);
int tr=get(r);
ll suml=(l-pow(10,tl-1)+1)*tl;
for(int i=1;i<tl;i++)
suml+=(pow(10,i)-pow(10,i-1))*i;
ll sumr=(r-pow(10,tr-1)+1)*tr;
for(int i=1;i<tr;i++)
sumr+=(pow(10,i)-pow(10,i-1))*i;
cout<<sumr-suml+tl<<endl;
}
}
原题链接:A-S 老师的公式_牛客挑战赛73 (nowcoder.com)
题目解释:
解题思路:
考察:数学、最大公因数、取模、找规律
错的原因好像是过程爆ll啦?
这里有几个知识点!
辗转相除法的原理:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。
0和任何数的最大公因数是本身
等差数列前N项和公式:
①Sn=n*a1+n(n-1)d/2。
②Sn=n(a1+an)/2。
其实这题写了好久都没写出来%>_<%… 首先尝试了一下找规律 只找出来一半:
参考题解:牛客挑战赛73题解(A-C) - 知乎 (zhihu.com)
然后发现最大公因数只可能是1-n的乘积,并且在2之后,最大最大公因数不会超过他们的和。
然后我就开始尝试!累加和之后,分解累加和的因数,只要分解的因数在1-n之间,更新最大公因数的答案,通过率95%……现在还是没发现什么原因呜呜
不对不对不对 好像有问题 但是现在脑子炸了想不动呜呜
正解就是:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int n;
cin>>n;
ll ans=0;
for(int i=1;i<=n;i++)
ans+=i;
ll sum=1;
for(ll i=1;i<=n;i++)
sum=sum*i%ans;
cout<< gcd(sum,ans);
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
构造一个数组,满足以下三个条件:
1. 数组的元素都是素数。
2. 数组所有元素相乘恰好等于xxx。
3. 数组任意相邻两个元素不等。
解题思路:
考察:数学、构造、分解质因数
因为每个数都可以分解为若干个素数相乘,关键在于能否排列成任意两个数不同。方法是取最多的和第二多两个数存入数组,判断最后两个数是否相同,如果相同则不存在。存在的
问题一是不知道怎么存数,
问题二是不知道怎么从大到小排序,
问题三是有一个误区不是一次性取完最多和第二多的数,而是保证每次操作都取最大和第二多的数,
问题四是注意特判的问题。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll> a;
ll sum=0;
typedef pair<ll,ll> PII;
struct cmp{
bool operator()(PII a,PII b){
return a.second>b.second;
}
};
void divide(ll x)
{
priority_queue<PII,vector<PII>,less<PII>> q;
for (ll i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
ll s = 0;
while (x % i == 0) x /= i, s ++ ,sum++;
// cout<<i<<" "<<s<<endl;
q.push({s,i});
}
if (x>1){
q.push({1,x});
sum++;
}
while(q.size())
{
// cout<<q.size()<<endl;
PII t1=q.top();
q.pop();
if(q.size()==0&&t1.first!=1)
{
cout<<-1;
return ;
}
else if(q.size()==0&&t1.first==1){
a.push_back(t1.second);
break;
}
PII t2=q.top();
q.pop();
// cout<<t1.first<<" "<<t1.second<<endl;
// cout<<t2.first<<" "<<t2.second<<endl;
// ll num=min(t1.first,t2.first);
a.push_back(t1.second);
a.push_back(t2.second);
if(t1.first-1>0)
{
q.push({t1.first-1,t1.second});
}
if(t2.first-1>0)
{
q.push({t2.first-1,t2.second});
}
}
cout<<sum<<endl;
for(ll i=0;i<a.size();i++)
cout<<a[i]<<" ";
}
int main()
{
ll x;
cin>>x;
if(x!=1)
divide(x);
else
cout<<"-1";
}
动态规划
原题链接:D-小红不想做完全背包 (hard)_牛客周赛 Round 39 (nowcoder.com)
题目解释:
有一个无穷大的背包,她希望往里面放若干个物品,使得最终所有物品的价值之和为ppp的倍数。小红想知道最终至少要放多少物品?
解题思路:
考察:取模 动态规划 最短路bfs
错的原因是emm没有分清楚01背包还是完全背包?
这里有几点总结:
01背包:
二维数组:两层循环从小到大 ,内层循环从上一层递推
一维数组:两层循环 外层从小到大 内层从大到小
完全背包:
二维数组:两层循环从小到大 ,内层循环从同一层递推
一维数组:两层循环 从小到大
第一种解法:因为最终要达到p的倍数,所以每个a【i】的贡献都是a【i】%p,最后判断能否把多个a【i】组合成a【0】。
方法一 用01背包更新最小值,【为什么用01背包还是没搞清楚,用内层循环从小到大wa了,改成从大到校正确】
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int a[N]={0};
int dp[N];
int main()
{
int n,p;
cin>>n>>p;
int f3=0,f2=0,f1=0;
for(int i=1;i<=n;i++){
int x;
cin>>x;
int k(x%p==0?6:x%p);
dp[x%p]=1;a[k]++;
}
for(int i=0;i<=p;i++)
{
for(int j=p;j>=0;j--)
{
if(dp[(i+j)%p]&&dp[i%p]&&dp[j%p])
{
dp[(i+j)%p]=min(dp[(i+j)%p],dp[i%p]+dp[j%p]);
}
if(dp[(i+j)%p]==0&&dp[i%p]&&dp[j%p])
{
dp[(i+j)%p]=(dp[i%p]+dp[j%p]);
}
}
}
cout<<dp[0];
}
方法二,因为求最小所以用宽度优先搜索就可以,这里当时没想到
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
typedef pair<int,int> PII;
int a[N];
int v[N]={0};
int dp[N];
int main()
{
int n,p;
cin>>n>>p;
queue<PII> q;
for(int i=1;i<=n;i++){
int x;
cin>>x;
a[i]=(x%p);
dp[x%p]=1;
if(!v[x%p]) q.push({x%p,1});
v[x%p]=1;
}
while(q.size())
{
auto t=q.front();
q.pop();
if(t.first==0)
{
cout<<t.second;
return 0;
}
for(int i=1;i<=n;i++)
{
int k=(a[i]+t.first)%p;
if(v[k]) continue;
v[k]=1;
q.push({k,t.second+1});
}
}
// cout<<dp[0];
}
原题链接:0牛牛的贪吃计划 - 蓝桥云课 (lanqiao.cn)
题目解释:
花费最短时间吃n块食物,每次只能吃每堆食物的最前面
解题思路:
考察:动态规划 前缀和
错的原因是当成贪来解决啦,这里局部最优解并不是所有的最优解
这里由于每次只能吃每堆食物的最前面,所以无论怎么组合都是两个数组的前n个和,所以用前缀和先计算出只吃a食物的前缀和,在计算b的前缀和,最后组合搜索一下
题解还有一种方法 但素好复杂也是利用前缀和然后dp好像,有兴趣自己去看
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+10;
int ansa[N];
int ansb[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int x; cin>>x;
ansa[i]=ansa[i-1]+x;
}
for(int i=1;i<=n;i++)
{
int x;cin>>x;
ansb[i]=ansb[i-1]+x;
}
int ans=1e9;
for(int i=0;i<=n;i++)
{
ans=min(ans,ansa[i]+ansb[n-i]);
}
cout<<ans;
}
dp
原题链接:E-小红的子序列求和_牛客周赛 Round 42 (nowcoder.com)
题目解释:
计算长度为k的子序列之和
解题思路:
考察:线性dp 【逆元,组合数】
比赛的时候我是考虑每个位置对答案额贡献了,用的是组合数思想但是这类题型我还没有掌握
这里给出另一种解法用线性dp
cnt【i】【j】 表示对于当前位置i可以构成长度为j的子序列的个数
dp【i】【j】 表示对于位置i构成长度为j子序列之和
状态更新好难想啊… 这里对于cnt【i】【j】
我们首先要初始化。因为这里子序列长度j:1~k。对于每个位置i都至少可以构成长度为1的子序列。所以这里令所有cnt[i][0]=1。注意一下,这里存储字符串的时候不能从下标0开始,不好进行状态转移,转移时会出界,所以这里不能用string直接存储
我们考虑转移的条件是,当长度j-1的子序列存在的时候,当前位置就可以与前面j-1长度的子序列构成长度为j的子序列长度,注意这里是累积所有长度为j 子序列的个数和,所以不要漏掉前面已经构成j长度子序列的个数
cnt[i][j]=cnt[i-1][j]+cnt[i-1][j-1]
对于dp[i][j]我们要累加之前已经构成j长度的子序列之和,在加上对于当前位置j的贡献。
当前位置的贡献=(长度j-1的和*10+当前位置的数字)*可以构成的新的长度为j的个数
对于个数的贡献这里为啥要用cnt【i-1】【j-1】 不知道啊不知道 用cnt【i】【j】好像会重复?
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
const int M=1e9+7;
#define int long long
int a[N];
int cnt[N][N];
int dp[N][N];
signed main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
char e; cin>>e;
a[i]=e-'0';
cnt[i][0]=1;
}
cnt[0][0]=1;//初始化
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
cnt[i][j]=(cnt[i-1][j]+cnt[i-1][j-1])%M;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*10+cnt[i-1][j-1]*a[i])%M;
}
}
cout<<dp[n][k];
}
图论
原题链接:P8794 [蓝桥杯 2022 国 A] 环境治理 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
求最短路的和小到一定程度需要多少天
解题思路:
考察:二分 最短路 完全图
这里用到了floyed 算法求最短路径 就是用来求图中任意两点最短路径的,floyd算法图文详解(全)_《羊卓的杨的算法笔记》_Quentin-CSDN博客
这里开始没有思考就直接模拟写题了,发现步骤大量重复。
每次都要求一遍最短路,最后果然超时了。完全没有想到这里也可以二分写。看了一边题目后发现可以用二分,当时想因为每条边减少的值是按周期一直循环的,当时二分的代码就以一周期为一个单位,反而把问题复杂化了。直接根据天数是可以判断出每条边减掉多少值的,但是这里要注意的点是这里是完全图,而且这里很麻烦的是每次只减掉与i相关的边,当【i】【j】--后【j】【i】也--,也就是说一个周期每个点要减去2,然后不足一个周期的这里n%m ,当点在范围里也要--,但是这里要注意一下,例如如果n%m==2,就是说明与0,1,2相关的都要减掉一边 ,那么【0】【3】只用减掉1遍,【0】【1】要减掉2遍
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int a[N][N];
int l[N][N];
int n,q;
int check(int x)
{
int d[N][N];
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
d[i][j]=a[i][j];
}
}
for(int i=0;i<n;i++)
{
int xx=(x/n)*2;
for(int j=0;j<n;j++)
{
d[i][j]=max(l[i][j],d[i][j]-xx);
int k=(x)%n;
if(i<k)
{
d[i][j]=max(l[i][j],d[i][j]-1);
}
if(j<k)
{
d[i][j]=max(l[i][j],d[i][j]-1);
}
}
}
for(int k=0;k<n;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
int ans=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
ans+=d[i][j];
}
}
return ans;
}
int main()
{
cin>>n>>q;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>a[i][j];
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>l[i][j];
}
}
int l=0,r=1e5+10;
int ans=-1;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)<=q)
{
r=mid;
ans=mid;
}
else l=mid+1;
}
cout<<ans;
}
记忆化搜索
原题链接:P8605 [蓝桥杯 2013 国 AC] 网络寻路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)B-逃跑路线_2024HUTrobocom省赛选拔赛 (nowcoder.com)P8605 [蓝桥杯 2013 国 AC] 网络寻路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
有多少种路线
解题思路:
考察:记忆化搜索 dfs bfs
我丢…因为害怕超时愣是没忘dfs这方面想,结果我出的题目内存超限啦?还没咋这样wa过,也算是一种进步…
这题直接搜索必定超时,故我们可以很快想到,已经遍历过能到达终点的点,肯定是不需要再次遍历了,因为能走的路径都已经走完了,故用一个数组记录某个点能到达终点的路径数目,一边便利一边更新。
没写过记忆化搜索,因为带返回值的递归函数好难┭┮﹏┭┮,这里记录下几个知识点吧。
方法一:
状态的更新关系要着重考虑到当前节点和子节点的关系
数组st【i】记录该点是否遍历过,用来判断cnt【i】可不可以直接返回答案。
数组cnt【i】记录某个点能到达终点的路径数量,注意这里要更新状态,我们在每次递归的时候,用一个ans记录当前的节点i的的路径数量,怎么记录?这里要用到dfs的返回值,dfs在遍历完i的每个子节点的时候,返回其路径数量。而当前节点i的路径数目是子节点路径之和,也就是在for循环里ans要累加所有子节点的返回值。最后更新cnt状态。
总的dfs返回值是,从某个起点出发的路径数量。
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
const int M=998244353;
typedef long long ll;
vector<int> a[N];
int d[N];
int c[N];
int st[N]={0};
int cnt[N]={0};
ll sum=0;
int dfs(int u)
{
if(st[u]) return cnt[u];
if(!c[u]) return 1;
st[u]=1;
ll ans=0;
for(int x :a[u])
{
ans=(ans+dfs(x))%M;
}
ans%=M;
cnt[u]=ans;
return ans;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++)
{
int from,to;
cin>>to>>from;
a[from].push_back(to);
d[to]++;
c[from]++;
}
for(int i=1;i<=n;i++)
{
if(!d[i])
{
sum=(sum+dfs(i))%M;
}
}
cout<<sum%M;
}
方法二:
用bfs写,这里怎么像递归一样保证先更新子节点呢?利用拓补排序。每遍历一个点让入度--,当入度为0的时候,使该点入队,这样可以保证已经入队的点都已经状态更新完毕,原理和dfs一样,最后累加就可以了
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
const int M=998244353;
typedef long long ll;
vector<int> a[N];
int d[N];
int c[N];
int cnt[N]={0};
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++)
{
int from,to;
cin>>to>>from;
a[from].push_back(to);
d[to]++;
c[from]++;
}
queue<int> q;
for(int i=1;i<=n;i++)
{
if(!d[i])
{
// sum=(sum+dfs(i))%M;
cnt[i]=1;
q.push(i);
}
}
ll sum=0;
while(q.size())
{
auto t=q.front();
q.pop();
for(int x : a[t])
{
cnt[x]=(cnt[x]+cnt[t])%M;
d[x]--;
if(!d[x])
{
q.push(x);
}
}
if(!c[t])
sum=(sum+cnt[t])%M;
}
cout<<sum;
}
原题链接:P8605 [蓝桥杯 2013 国 AC] 网络寻路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目解释:
找一共有多少个不同的路径,满足三条路,中转点不重复的条件
解题思路:
考察:dfs 排列组合性质?
这题还是挺简单的,用dfs写出来了注意下条件问题,但是最后一个数据wa了呜呜。
题解还提供了另一种思路,因为这里只有三条边。我们可以利用一下高中概率排列组合知识。记录每个点的入度。遍历每条边,作为中转边,然后一共有(d【u】-1)*(d【v】-1)的路,因为这里是无向图,倒过来也算,所以还需乘以2。注意一下只要中转边的其中一个入度为1就作为中转边
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int h[N],e[N],ne[N];
int st[N];
int n,m;
int idx=0;
int f=0;
int sum=0;
int a[10];
void dfs(int u,int s)
{
if(s==3)
{
sum++;
return ;
}
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(s<2&&j==f)continue;
if(st[j]==0)
{
st[j]=1;
a[s+1]=j;
dfs(j,s+1);
st[j]=0;
}
}
}
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int main()
{
int n,m;
cin>>n>>m;
memset(h,-1,sizeof(h));
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
for(int i=1;i<=n;i++)
{
f=i;
memset(st,0,sizeof(st));
a[0]=i;
dfs(i,0);
}
cout<<sum;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
typedef pair<int,int> PII;
vector<PII> e;
int d[N]={0};
int st[N];
int n,m;
int idx=0;
int f=0;
ll sum=0;
int a[10];
int main()
{
int n,m;
cin>>n>>m;
// memset(h,-1,sizeof(h));
int k=m;
while(m--)
{
int a,b;
cin>>a>>b;
e.push_back({a,b});
d[a]++;
d[b]++;
}
for(int i=0;i<k;i++)
{
if(d[e[i].first]>1&&d[e[i].second]>1)
sum+=((d[e[i].first]-1)*(d[e[i].second]-1)*2);
}
cout<<sum;
}
原题链接:E-魔法之森的蘑菇_牛客周赛 Round 37 (nowcoder.com)
题目解释:
给一个图,只能沿着一个方向走,遇到*可以转弯,遇到#不能走,求s到t的最短路
解题思路:
考察:bfs
这题是基础的bfs变形,因为有方向的限定,所以难点在方向的记录,用距离数组dist[x][y][4]记录某个点某个方向的最短距离,标记数组同理。至于为什么要记录方向,思考了很久最后想出来的是可以参考一下高架桥,不同方向上的最短距离更新不一样的,不记录方向肯定wa。最后是看了题解都还是写了很久,卡在状态更新上面,对标记数组运用还是不熟练,不知道该放在里。注意,对于不同状态的更新,例如这一题沿着一个方向还是可以拐弯要最好在同一层更新状态,不然就很容易跳过漏点。多思考清楚逻辑关系,在敲代码!
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
char a[1010][1010];
int dx[4]={1,0,-1,0};//下 右 上 左
int dy[4]={0,1,0,-1};
PII s,t;
int n,m;
int dist[1010][1010][4];
int v[1010][1010][4];
typedef struct{ int x,y,d; }A;
map<int,PII> mp;
int check(int x,int y)
{
if(x>=1&&x<=n&&y>=1&&y<=m)
return 1;
return 0;
}
void bfs()
{
queue<A> q;
// q.push({t.first,t.second,});
memset(dist,0x3f,sizeof(dist));
memset(v,0,sizeof(v));
for(int i=0;i<4;i++)
{
q.push({s.first,s.second,i});//4个方向
dist[s.first][s.second][i]=0;
}
while(q.size())
{
auto t=q.front();
q.pop();
int x=t.x,y=t.y,d=t.d;
int nx=x+dx[d]; int ny=y+dy[d];
if(!check(nx,ny)||v[nx][ny][d])continue;
if(a[nx][ny]=='#'||dist[nx][ny][d]<dist[x][y][d]+1) continue;
dist[nx][ny][d]=dist[x][y][d]+1;
v[nx][ny][d]=1;
q.push({nx,ny,d});
// cout<<x<<" "<<y<<endl;
if(a[nx][ny]=='*')
{
v[nx][ny][d]=1;
//
for(int i=0;i<4;i++)
{
if(mp[d].first!=-dx[i]&&mp[d].second!=-dy[i])
{
int xx=x+dx[i];
int yy=y+dy[i];
// cout<<nx<<" "<<ny<<endl;
v[nx][ny][i]=1;
dist[nx][ny][i]=dist[x][y][d]+1;
q.push({nx,ny,i});
}
}
}
}
int sum=1e9;
for(int i=0;i<4;i++)
sum=min(sum,dist[t.first][t.second][i]);
if(sum!=1e9)
cout<<sum<<endl;
else cout<<-1<<endl;
}
int main()
{
int k;
cin>>k;
while(k--)
{
mp[0]={1,0};
mp[1]={0,1};
mp[2]={-1,0};
mp[3]={0,-1};
cin>>n>>m;
int x,y;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
if(a[i][j]=='S')
{
s={i,j};
}
if(a[i][j]=='T')
{
t={i,j};
}
}
}
bfs();
}
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
给定一个n进制数m,问是否能被n-1整除。
解题思路:
考察:数学性质 取模 同余
方法一:同余式 若 A=a+b,则A%m=a%m+b%m;
即10进制下,891 能不能别(4-1)整除,800 % 3 + 90 % 3 + 1 % 3 ≡ 891 % 3。将m转化为10进制不断求余,就不会爆int啦
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int f[]={10,11,12,13,14,15};
int main()
{
ll n;
string m;
cin>>n;
cin>>m;
ll sum=0;
int t=1;
for(int i=m.size()-1,k=0;i>=0;i--,k++)
{
if(m[i]>='0'&&m[i]<='9')
sum+=((m[i]-'0')*t)%(n-1);
else
sum+=((m[i]-'A'+10)*t)%(n-1);
t*=t;
}
if(sum%(n-1)==0)
{
cout<<"YES";
}
else cout<<"NO";
}
方法二:任意一个n进制除以n-1的余数,等于它各位数字之和除以n-1的余数
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int f[]={10,11,12,13,14,15};
int main()
{
ll n;
string m;
cin>>n;
cin>>m;
ll sum=0;
for(int i=m.size()-1,k=0;i>=0;i--,k++)
{
if(m[i]>='0'&&m[i]<='9')
sum+=(m[i]-'0');
else
sum+=(m[i]-'A'+10);
}
if(sum%(n-1)==0)
{
cout<<"YES";
}
else cout<<"NO";
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
小红拿到了一个长度为n的数组,她准备进行一次修改,可以将数组中任意一个数修改为不大于 ppp 的正整数(修改后的数必须和原数不同),并使得所有数之和为x 的倍数。
小红想知道,一共有多少种不同的修改方案?
解题思路:
考察:数学性质 取模
暴力求解会超时,要对其取模优化,要使得数列修改之后的和为x的k倍。每个a[i]的贡献为a[i]%x ,对于每个a[i],sum的变化为sum-a[i]+p,p的范围为1~p。我们求出使sum变为p的倍数所需要的数 k,判断p是否能达到k,若可以即p的贡献为(p-k)/x+1个。最后考虑不修改的情况是否是合法的,如果是则方案数减1。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N];
int main()
{
int n,p,x;
cin>>n>>p>>x;
ll sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum=(sum+a[i])%x;//a[i]的贡献为 a[i]%x
}
ll cnt=0;
for(int i=1;i<=n;i++)
{
int ans=(sum-a[i]%x+x)%x;//sum减去a的贡献 +x防止sum变成负数
int k=x-ans;//k为变成x的倍数需要的值
int t=p-k;//t为 使ans变成x的倍数 还可以贡献的价值
if(t<0) continue;//没有合法的方案
cnt=cnt+t/x+1;//加上合法的方案数 t/x表示剩余的价值还可以使变成k的方案次数,加上之前的1次
if(sum%x==0&&a[i]<=p) cnt--;//判断不修改是否合法
}
cout<<cnt;
}
题目解释:
有一个长度为n的单元格,每个单元格上都有一个整数,最开始都是0. 一开始芯片在第一个位置,每当一个回合结束时(最开始也视为一个回合),芯片所在的单元格的整数+1.每回合结束时我们都有两个操作:
操作1:将芯片移动到下一个单元格(例如,如果芯片在 i单元格,则将其移动到 i + 1单元格)。如果芯片在最后一格,则无法进行此操作;
操作2:选择任意一个 x单元格,将芯片传送到该单元格。可以选择芯片当前所在的单元格;
每个单元格都有一个目标整数c[i]你的目标就是让每个单元格上的整数变为c[i],并且操作尽可能少的操作2,问最少使用多少次操作2可以让单元格上的整数都达成目标。
解题思路:
考察:贪心、数学
观察发现如果c[i] >= c[i + 1]那么每次使用操作2传送回 i 的时候能用操作1把芯片移到i+1的格子。也就是说如果有这么一个连续子数组c[i] >= c[i + 1] >= c[i + 2] ...答案就是芯片传送回第i个格子的次数。可以将整个数组划分为若干段这样不上升的子数组。一旦c[i] < c[i + 1]就是新的一段的开始。需要注意的细节是我们在走c[i]的时候能把c[i + 1]也给走了,也就是新的一段需要减去上一段的最后一个元素。由于最开始就在第一个位置,所以需要将最开始的上一段置为1方便统计答案。
a[i] - a[i-1] > 0,贡献值a[i] - a[i-1],对于a[1]特殊处理一下,a[1]--,因为a[1]直接先操作了一次
参考:https://www.cnblogs.com/qdhys/p/17855152.html
#include<bits/stdc++.h>
using namespace std;
const int N = 100010, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int,int> pii;
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = n; i > 0; i--) a[i] -= a[i - 1];
a[1]--;
ll ans = 0;
for (int i = 1; i <= n; i++)
if (a[i] > 0)
ans += a[i];
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _;
_ = 1;
cin >> _;
while(_--)
{
solve();
}
return 0;
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
解题思路:
考察:递推 数学 快速幂
数据范围较小,根据公式用快速幂递推即可。
函数 logX(Y):求x为底y的对数值
log(Y) :求e为底的y的对数值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[110];
ll qmi(ll a,ll k,ll p)
{
ll res=1%p,t=a;
while(k)
{
if(k&1){
res=res*t%p;
}
t=t*t%p;
k>>=1;
}
return res;
}
int main()
{
int f1,f2,p,a,n;
cin>>f[1]>>f[2]>>p>>a>>n;
for(int i=3;i<=n;i++)
{
f[i]=log2(qmi(f[i-1],f[i-2],p)+1)+a;
}
for(int i=1;i<=n;i++)
cout<<f[i]<<" ";
}
树形dp
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
游游拿到了一棵树,树的每条边有边权。游游准备选择一些边染成红色,她希望不存在两条染红的边共用同一个点,且最终染红边的权值之和尽可能大。你能帮帮她吗?
注:所谓树,即不包含重边、自环和回路的无向连通图
解题思路:
考察:树形dp
第一次写树形dp (~ ̄▽ ̄)~ (๑╹◡╹)ノ"""
dp[i][1] 表示i节点与其父节点的这条边已经选了的最大价值
dp[i][0] 表示i节点与其父节点的这条边没有选的最大价值
首先建树,注意是无向图建边要双向的。由于树的每个节点必只存在一个父节点,用pa[i]记录节点i的父亲节点【这里是用来判断当前节点是否是叶子节点。我们用dfs从跟递归遍历这棵树,并同时记录当前节点的父亲节点,若当前节点的父亲节点为当前节点即j=pa[u],则该节点为叶子节点】
在遍历的同时将dp[i][1]初始化。
初始化完之后开始状态转移。我们用u表示当如果前节点,pa表示父亲节点,v表示子节点。
我们要将dp[v]子节点转移到当前节点。可以这样考虑,对于u与pa这条边存在两种情况选与不选。①如果选则v与u这条边一定不能选,所以只需要把所有v与u不选的子树最大值累加,即dp[v][0]累加起来。
状态转移方程为dp[u][1]+=dp[v][0]。
②如果不选,那么v与u这条边就有可能选。我们只需遍历找出dp[v][1]的最大值,并累加上其他子树dp[v][0]的价值。但是要注意我们在加dp[v][1]不可直接加需要减掉dp[v][0]再加起来,因为后面累加dp[v][0]时会重复累加。so
状态转移方程为dp[u][0]+=dp[v][0]+dmax。【dmax=dp[v][1]-dp[v][0]】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010;
int h[N],e[N],ne[N],w[N];
int idx=0;
ll dp[N][2];
int pa[N];
void dfs(int u,int k)
{
dp[u][1]=k;
ll d=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(j==pa[u]) continue; //判断是叶子结点
pa[j]=u;
dfs(j,w[i]);
d=max(d,dp[j][1]-dp[j][0]);
dp[u][0]+=dp[j][0];
dp[u][1]+=dp[j][0];
}
dp[u][0]+=d;
}
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int main()
{
int n;
cin>>n;
memset(pa,-1,sizeof(pa));
memset(h,-1,sizeof(h));
for(int i=1;i<n;i++)
{
int u,v,c;
cin>>u>>v>>c;
add(u,v,c);
add(v,u,c);
}
dfs(1,0);
cout<<dp[1][0];
}
原题链接:登录—专业IT笔试面试备考平台_牛客网D-小红的树上删边_牛客周赛 Round 42 (nowcoder.com)登录—专业IT笔试面试备考平台_牛客网
题目解释:小红拿到了一棵树,她希望删除一些边,使得每个连通块的大小是偶数。请你帮小红计算最多删除多少条边。
解题思路:
考察:树形dp
我丢…差一点就写出来了啊…
在这里比赛的时候有一点迷惑,因为这类题型写的很少。
一是叶子结点的判断:当孩子=父亲的时候,即该节点是叶子结点。
二是递归的顺序不是很明白:当遍历完所有的孩子【递归完成】,即将整棵树需要初始化的地方完成了。接着就可以寻找逻辑关系递推!
三是不知道该如何存储有效信息,当时比赛的时候判断集合的点数是需要记录的,以及删的边数还有 当前遍历到的节点。加上以前做题经验的局限性,总想着用二维数组记录,然后就思考了很久。最后才思考出来,这题其实并不需要更新状态,当构成2个就删掉与父节点的边,然后累加。
还有需要注意的地方是,当累计完孩子节点就可以判断了,并不是要等到统计完当前节点,否则会漏删
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],e[4*N],ne[4*N];
int dp[N];
int p[N];
int idx=0;
//计算最多删除多少条边 怎么判断每条边能不能删 用dp【i】记录当前联通快大小??
//这里有 个点需要存 1删除边数 2联通快的大小 3
// 只要已达到偶数就删
int cnt=0;
void dfs(int u,int f)
{
// cout<<u<<endl;
dp[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(p[u]==j) continue;//叶子节点
p[j]=u;
dfs(j,f);
// 已经遍历完孩子
if(dp[j]%2==0) cnt++;
else
dp[u]+=dp[j];
}
}
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int main()
{
int n;
cin>>n;
memset(h,-1,sizeof(h));
for(int i=0;i<n-1;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
if(n&1)
{
cout<<-1;
return 0;
}
dfs(1,0);
cout<<cnt<<endl;
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
解题思路:
考察:bfs 二分
一开始的思路是二分+dfs,但是超时了,又不知道怎么优化【有待提高,似乎可以用数组存一下?】。就想到二分+bfs,但是一直过不去,忽略了一个细节,(1,1)一开始入队的并没有计算从地面到(1,1)的距离,所以二分的范围l不是0!
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef pair<PII,int> P;
int a[510][510];
int st[510][510];
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int n,m;
int f=0;
int bfs(int s)
{
memset(st,0,sizeof(st));
queue<PII> q;
q.push({1,1});
st[1][1]=1;
while(q.size())
{
auto t=q.front();
q.pop();
if(t.first==n&&t.second==m) {
f=1;
break;
}
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=1&&x<=n&&y>=1&&y<=m&&a[x][y]-a[t.first][t.second]<=s&&st[x][y]==0)
{
st[x][y]=1;
q.push({x,y});
}
}
}
return st[n][m];
}
int main()
{
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
sum=max(a[i][j],sum);
}
}
int l=a[1][1],r=sum;//注意!l不能是0 TAT
while(l<r)
{
f=0;
int mid=r+l>>1;
if(bfs(mid)) r=mid;
else l=mid+1;
}
cout<<l;
}
方法二:不用二分 用dp记录状态
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef pair<PII,int> P;
int a[510][510];
int st[510][510];
int dp[510][510];
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int n,m;
int f=0;
int bfs()
{
memset(st,0,sizeof(st));
queue<PII> q;
q.push({1,1});
dp[1][1]=a[1][1];
st[1][1]=1;
while(q.size())
{
auto t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x<1||y<1||x>n||y>m)continue;
int k=max(dp[t.first][t.second],a[x][y]-a[t.first][t.second]);
if(st[x][y]!=0){
if(k>=dp[x][y])continue;
}
st[x][y]=1;
dp[x][y]=k;
q.push({x,y});
}
}
return dp[n][m];
}
int main()
{
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
sum=max(a[i][j],sum);
}
}
cout<<bfs();
}
原题链接:登录—专业IT笔试面试备考平台_牛客网
题目解释:
班上总共有n个人,第i个同学要点评第ai个同学的作业。当你去提醒某个同学k时,你同时会把同学k的作业点评了。而一个被提醒的同学k,会去点评同学ak的作业,并且去提醒同学ak要去点评下一个人。
求每个人都点评到所提醒的最小人数
解题思路:
考察:图
一开始的思路是查并集,提醒的最少次数是多少个不同的集合【sorry…完全不对,因为这个是单向的并不是双向,提醒a,a会提醒b,但是b不会反过来提醒a,所以不能简单合并】
第二个思路,逐渐发现提醒的关系很像树的结构,其实是单向有环图?尝试暴力搜索。每个人我都提醒一遍,求最小的提醒次数。写啦好久,最后超时了……
第三个思路,是有一点拓扑排序的思想。记录每人的入度(即被提醒的次数,入度为0肯定是要被提醒的)后来比赛的时候就卡住了【emm…我在想入度不为0那怎么办?是每个又遍历一遍嘛?有环存在怎么办?怎么都遍历一遍?】so,最后没写出来TAT
正解:思路和第三个思路差不多,不存在入度为0的点,则必定有环。若存在特殊情况用外层让一个点入队,sum++。然后遍历所有可以走的点。将入度为0的点开始可以到达的点都记录一遍,同时外层循环判断是否还有遗漏的点。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
vector<int> a[N];
int st[N];
int d[N]={0};
int main()
{
int n;
cin>>n;
memset(st,0,sizeof(st));
for(int i=1;i<=n;i++)
{
int k; cin>>k;
a[i].push_back(k);
d[k]++;
}
int sum=0;
queue<int> q;
int f=0;
for(int i=1;i<=n;i++) if(d[i]==0) {
q.push(i); sum++; st[i]=1;f=1;
}
for(int i=1;i<=n;i++)
{
if(f==0)
{
if(st[i]==0)
{
q.push(i);
sum++;
st[i]=1;
}
else{
continue;
}
}
while(q.size())
{
int t=q.front();
q.pop();
for(int i=0;i<a[t].size();i++)
{
int j=a[t][i];
if(st[j]) continue;
st[j]=1;
q.push(j);
}
}
f=0;
}
cout<<sum;
}
简易版本others ac代码:
优化1:这里图的关系很简单,可以用一个数组直接记录指向节点即可
优化2:dfs优化【思路清晰多啦】
#include<bits/stdc++.h>
const int N=5e5+10;
int p[N],d[N];
bitset<N>st;
int n;
void dfs(int x){
st[x]=true;
if(!st[p[x]])dfs(p[x]);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>p[i],d[p[i]]++;
int res=0;
queue<int>q;
for(int i=1;i<=n;i++)if(!d[i])q.push(i),res++;
while(q.size()){
auto t=q.front();
q.pop();
dfs(t);
}
for(int i=1;i<=n;i++){
if(st[i])continue;
dfs(i);
res++;
}
cout<<res<<endl;
return 0;
}
原题链接:大师 - 洛谷
题目解释:
求等差数列的数量
解题思路:
考察:动态规划、dp
这题看题解写的,所以还是有点不太明白 。
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
const int M=998244353;
int a[N];
int dp[1010][N];//dp[i][j]表示每一个以i结尾的数等差为j的方案数的数量
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int ans=0; int p=20000;//防止数组下标出现负数
for (int i=1;i<=n;i++)
{
ans++;//累加只有一个数的等差数列
for(int j=i-1;j;j--)
{//对于每个i
dp[i][a[i]-a[j]+p]+=dp[j][a[i]-a[j]+p]+1;//往下更新状态
dp[i][a[i]-a[j]+p]%=M;
ans+=dp[j][a[i]-a[j]+p]+1;//ans每次加上dp[i][j]的内容,+1是加上只有i与j两个等差数列的情况【因为ans累加的是j,j的范围是1~n-1?】
ans%=M;
}
}
cout<<ans;
}
原题链接:集合求和 - 洛谷
题目解释:
求所有子集的和
解题思路:
考察:数学、排列组合
我们考虑不同元素个数的子集情况,
一个元素的子集:对于每一个元素来说,从剩下的元素中选0个即C(0,n-1);
两个元素的子集:对于每个元素来说,从剩下的元素中选1个元素作为一个子集,即C(1,n-1);
三个元素的子集:对于每个元素来说,从剩下的元素中选2个元素作为一个子集,即C(2,n-1);
以此类推
对于所有元素的子集,(1-n)有
标签:int,ll,cin,long,错题,include,dp From: https://blog.csdn.net/qq_73746764/article/details/134621455