2025牛客寒假算法基础集训营1
寒假第一场牛客算法训练营。
过题数 5/13,补题数 10/13
赛时简单难度全出,中等难度出一题。
第二道中等难度题是道原题,不过没有搞清楚排序的规律,导致WA8发也没有做出来。
第一道中等难度的题能做出来还是比较欣喜的,因为我赛前复习了 multiset 的用法,多看题是好的,希望自己不要废弃掉这个习惯。
A.茕茕孑立之影
给你一个数组,要求找到一个不大于 10^18 的正整数 x,满足 x 和数组中任意一个元素都是互不为倍数的关系,所有的数都不是 x 的倍数,x 也不是任何数的倍数。
如果数组中有 1 的话,那么所有的数都是 1 的倍数,没有答案输出 -1,否则,直接输出一个很大的质数就好。a[i]<=1e9,我们输出1e9+7就好。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
void miaojiachun()
{
int n;
cin >> n;
vector<int> a(n + 1);
int flag = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
if (a[i] == 1)
{
flag = 1;
}
}
if (flag == 1)
{
cout << "-1" << endl;
return;
}
cout<<mod<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
B.一气贯通之刃
小红拿到了一棵树,她想请你寻找一条简单路径,使得这条路径不重不漏的经过所有节点。如果不存在这样的简单路径,则直接输出 −1。
简单路径是指这样一条路径,其经过的顶点和边互不相同。
简单路径不能经过一个点两次,因此如果一个点有 3 条或者更多条边,就一定会有走不到的边。
因此这棵树一定会是一条链,那么我们只需要找到链的两个端点即可。
我们只要计算每个点的度数,答案就是度数为 1 的两个点(并且度数为 1 的点有且只有两个)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
int n;
cin >> n;
map<int, vector<int>> g;
vector<int> de(n + 1, 0);
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
de[u]++;
de[v]++;
}
vector<int> node;
for (int i = 1; i <= n; ++i) {
if (de[i] == 1) {
node.push_back(i);
}
}
if (node.size() != 2) {
cout << -1 << endl;
} else {
cout << node[0] << " " << node[1] << endl;
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
D.双生双宿之决
判断一个数组长度是否为偶数,是否只含两种元素,且这两种元素的出现次数是否相同。
模拟即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(n+1);
map<int,int>u;
vector<int>b;
for(int i=1;i<=n;i++){
cin>>a[i];
if(u[a[i]]==0){
b.push_back(a[i]);
}
u[a[i]]++;
}
if(b.size()==2 and u[b[0]]==u[b[1]] and n%2==0){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
G.井然有序之衡
小红拿到了一个数组,她可以进行任意次以下操作:选择两个元素,使得其中一个加 1,另一个减 1。
小红希望最终数组变成一个排列,请你帮助他确定这能否实现。如果可以实现的话,还需要求出最小操作次数。
首先进行排序,如果排序之后就是一个排列,那么就直接输出 0 就好了。否则我们记录每个数组要加多少或减多少。如果加和减的大小不同,我们输出 -1,因为不可能实现。否则,输出要加的数的大小,即为次数
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(n+1);
vector<int>b(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=i;
}
sort(a.begin()+1,a.end());
int kk=0;
for(int i=1;i<=n;i++){
if(a[i]!=b[i]){
kk=1;
}
}
if(kk==0){
cout<<"0"<<endl;
return;
}
int sum1=0,sum2=0;
for(int i=1;i<=n;i++){
if(a[i]<b[i]){
sum1+=b[i]-a[i];
}if(a[i]>b[i]){
sum2+=a[i]-b[i];
}
}
if(sum1!=sum2){
cout<<"-1"<<endl;
return;
}
cout<<sum1<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
M.数值膨胀之美
定义一个数组的极差:数组的元素最大值减去最小值。
一个数组,进行恰好一次操作:选择一个非空区间,将其中所有元素都乘以 2。请最小化数组的极差。
multiset的插入和删除都是log级的操作。所以我们用这个来对数组状态进行更新不会超时。
首先如果数组所有元素都相等我们输出 0 就好。
否则我们要做的操作是:使用两个数组,一个进行更改,一个进行记录。首先最大值是不能变的,我们要做的是找到数组中的最小值,以这个最小值的下标为基准,向左右两边进行范围的扩大,将范围内的数扩大为原来的两倍,每次更改都更新一遍现有极差,如果极差增大了我们就进行还原操作。我们先用multiset进行数组的全部存入。因为他是自动按有序存储,且可以储存重复元素,所以我们可以在这里找到数组中的所有元素。
因为一定要进行更改,所以最小值一定会扩大为原来的两倍。我们插入新值,当前的极差就是初始极差,之后利用指针l,r,以最小值下标为基准进行遍历,不断更新极差。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
const int mod = 998244353;
// const int mod = 1e9 + 7;
#define x first
#define y second
//二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(n+1);
vector<int>b(n+1);
multiset<int>c;
int max1=0,min1=inf/2;
int k=0;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
c.insert(a[i]);
max1=max(max1,a[i]);
if(min1>a[i]){
min1=a[i];
k=i;
}
}
int flag=0;
for(int i=1;i<n;i++){
if(a[i]!=a[i+1]){
flag=1;
}
}
if(flag==0){
cout<<"0"<<endl;
return;
}
c.erase(c.find(b[k]));
b[k]*=2;
c.insert(b[k]);
int ans=*c.rbegin()-*c.begin();
int l=k-1,r=k+1;
while(1){
int flag1=0,flag2=0;
if(l>=1 and a[l]!=*c.rbegin()){
c.erase(c.find(a[l]));
c.insert(a[l]*2);
int ans1=*c.rbegin()-*c.begin();
if(ans1<ans){
flag1=1;
ans=ans1;
l--;
}else{
c.erase(c.find(a[l]*2));
c.insert(a[l]);
}
}if(r<=n and a[r]!=*c.rbegin()){
c.erase(c.find(a[r]));
c.insert(a[r]*2);
int ans1=*c.rbegin()-*c.begin();
if(ans1<ans){
flag2=1;
ans=ans1;
r++;
}else{
c.erase(c.find(a[r]*2));
c.insert(a[r]);
}
}
if(flag1==0 or flag2==0){
break;
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
H.井然有序之窗
一个比较经典的贪心,
i 这个数必须在所有 l≤i,r≥i 的区间中选到,如果有多种选择,选择 r 最小的区间一定不会使得答案变劣。
一个例子:有两个区间 [1,1],[1,2] ,我们 1 如果在第二个区间中选,会导致 2 这个数无法选到。
我们可以使用优先队列维护右端点最小的区间,对区间按左端点排序后,可以从前往后将左端点小于 i 的区间加入优先队列,然后取出右端点最小的区间。
若右端点小于 i ,理论上应该将这个区间丢弃,找到第一个满足右端点大于等于 i 的区间,但由于区间不能浪费,因此此时一定无解。若优先队列为空,同样无解。
时间复杂度 O(nlogn) 。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
struct pp{
int l,r,k;
};
void miaojiachun()
{
int n;
cin>>n;
vector<pp>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i].l>>a[i].r;
a[i].k=i;
}
sort(a.begin()+1,a.end(),[&](const pp &a,const pp &b)->bool{
if(a.l!=b.l) return a.l<b.l;
return a.r<b.r;
});
priority_queue<PII,vector<PII>,greater<PII>>pq;
vector<int>res(n+1);
int pos=1;
for(int i=1;i<=n;i++){
while(pos<=n and a[pos].l<=i){
pq.emplace(a[pos].r,a[pos].k);
pos++;
}
while(!pq.empty() and pq.top().x<i){
pq.pop();
}
if(pq.empty()){
cout<<"-1"<<endl;
return;
}
auto[r,idx]=pq.top();
pq.pop();
res[idx]=i;
}
for(int i=1;i<=n;i++){
cout<<res[i]<<" ";
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
J.硝基甲苯之袭
打表发现 x,y(x<y) 满足 x⊕y=gcd(x,y) ,当且仅当 y=x+gcd(x,y) ,且 x 是偶数。
我们枚举每个偶数 x ,和 x 的因子 t 得到 y=x+t ,判断上述式子是否合法,若合法统计答案即可。
枚举 x 和 x 的因子 t ,那么 y=x⊕t ,再检查 gcd(x,y)=t 是否合法,若合法统计答案。时间复杂度 O(n*sqrt(n)).
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(2*n+1);
for(int i=1;i<=n;i++){
int x;
cin>>x;
a[x]++;
}
int ans=0;
for(int i=2;i<=n;i+=2){
for(int j=1;j*j<=i;j++){
if(i%j) continue;
int x=i,y=j;
y+=x;
if((x^y)==gcd(x,y)) ans+=1ll*a[x]*a[y];
if(j*j!=i){
y=i/j;
y+=x;
if((x^y)==gcd(x,y)) ans+=1ll*a[x]*a[y];
}
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
// cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
E.双生双宿之错
假设我们确定了双生数组的两种元素分别为 ( x ) 和 ( y ),且 ( x < y )。
如果数组是有序的,将前一半变成 ( x ),后一半变成 ( y ),操作次数为前一半元素与 ( x ) 的差值的绝对值之和,加上后一半元素与 ( y ) 的差值的绝对值之和。
将数组分成左右两部分,分别求值。问题转化为:将所有数变成某个值 ( t ) 的最小操作次数。
一个显然的结论是:取 ( t ) 为这个数组的中位数。
感性理解:如果所有数字都变成了 ( t ),现在要使得所有数字变成 ( t + 1 ),需要让原本小于等于 ( t ) 的数字都加一次(记个数为 ( l )),原本大于 ( t ) 的数字少加一次(记个数为 ( r )),代价是 ( l - r ),其中 ( l + r = n )。当 ( t ) 为中位数时,( l ) 会大于等于 ( r ),代价就变成了非负整数。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a.begin()+1,a.end());
auto get=[&](auto x,auto y){
auto ans=0ll;
for(int i=1;i<=n/2;i++){
ans+=abs(a[i]-x);
}
for(int i=n/2+1;i<=n;i++){
ans+=abs(a[i]-y);
}
return ans;
};
auto check=[&](int x,int y){
auto t=0ll;
if(a[x]!=a[y]) t=get(a[x],a[y]);
else t = min(get(a[x] - 1, a[y]), get(a[x], a[y]+1));
return t;
};
int t=n>>1;
int x=0,y=0;
if(t & 1){
x=(t+1)>>1;
y=t+x;
}else{
x=t>>1;
y=t+x+1;
}
cout<<check(x,y)<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
C.兢兢业业之移
我们需要把所有的 1 都推到左上角。可以先将所有的 1 往最上方推,再将所有的 1 往最左边推,此时所有的 1 会形成一个类似上三角矩阵的阶梯。
接下来,我们需要将右边多出的 1 和下边多出的 1 放进左上角。从边缘的 1 开始,逐步向中间靠拢即可。
操作次数大致如下:
- 每个 1 从最底下移动到最上方,需要 ( 2n ) 次操作。
- 每个 1 从最右边移动到最左边,也需要 ( 2n ) 次操作。
- 从边角往中间靠拢,还需要 ( 2n ) 次操作。
总操作次数为 ( 4n )。假设 1 的个数为 ( n^2 / 4 ),则总操作次数为 ( n^3 )。
理论上,操作次数超过了题目的要求,但实际上并非每个 1 都需要走满 ( 2n ) 步。例如,如果一个 1 从右下角走到左上角走了 ( 2n ) 步,必然会使得其他 1 不需要走满 ( 2n ) 步。因此,每个 1 的移动步数并不是独立的,实际操作次数不会超过题目的要求。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
char c[105][105];
struct xx{
int a,b,c,d;
};
void miaojiachun()
{
int n;
cin>>n;
vector s(n+2,string(n+2,'1'));
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]='1'+s[i]+'1';
}
vector<vector<int>>ans;
auto go=[&](int x,int y,char c){
int dx=0;
dx-=c=='U';
dx+=c=='D';
dx+=x;
int dy=0;
dy-=c=='L';
dy+=c=='R';
dy+=y;
swap(s[x][y],s[dx][dy]);
ans.push_back({x,y,dx,dy});
};
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=i;k>=1;k--){
if(s[k][j]=='1' and s[k-1][j]=='0'){
go(k,j,'U');
}else{
break;
}
}
}
}for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=j;k>=1;k--){
if(s[i][k]=='1' and s[i][k-1]=='0'){
go(i,k,'L');
}else{
break;
}
}
}
}
for(int i=n/2;i>=1;i--){
for(int j=n;j>n/2;j--){
if(s[i][j]=='0'){
continue;
}
int x=0,y=0;
for(int k=1;k<=n/2;k++){
for(int l=1;l<=n/2;l++){
if(s[k][l]=='1') continue;
x=k;
y=l;
break;
}
}
for(int k=i;k<x;k++){
go(k,j,'D');
}
for(int k=j;k>y;k--){
go(x,k,'L');
}
}
}
for(int i=n;i>n/2;i--){
for(int j=n/2;j>=1;j--){
if(s[i][j]=='0') continue;
int x=0,y=0;
for(int k=1;k<=n/2;k++){
for(int l=1;l<=n/2;l++){
if(s[k][l]=='1') continue;
x=k;
y=l;
break;
}
}
for(int k=j;k<y;k++){
go(i,k,'R');
}
for(int k=i;k>x;k--){
go(k,y,'U');
}
}
}
cout<<ans.size()<<endl;
for(auto& i:ans){
for(auto&j:i){
cout<<j<<" ";
}
cout<<endl;
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
F.双生双宿之探
首先要观察双生数组的性质。可以发现,可能存在双生数组的区间一定是一段只包含两种数字的区间。假设区间中的两种数字分别是 x 和 y ,我们称这种区间为“ xy -好区间”。
“ xy -好区间”应该是极长的。如果一个较长的“ xy -好区间”包含了另一个较短的“ xy -好区间”,我们需要舍去较短的那个,而在较长的区间中进行计算。(具体原因暂不讨论)
将所有极长的“ xy -好区间”排序后会发现,连续的 3 个好区间中,第 1 个和第 3 个一定是不相交的。例如,假设第一个区间是 x 和 y ,第二个区间是 y 和 z ,那么第三个区间如果与第一个区间相交,就必然包含 x 、 y 、 z 三种数字。因此,这些极长的“ xy -好区间”的总长度不超过 2n 。
一个“ xy -好区间”中有多少个双生数组呢?
假设一个数组只包含 1 和 -1,那么双生数组的定义就等价于区间和为 0 的区间。
如何解决这个问题呢?
我们记数组的前缀和为 f ,那么 f_r - f_l = 0 当且仅当 f_r = f_l 。因此,可以用一个容器记录 f_r 的出现次数,从而计算以 r 为结尾的“好区间”有多少个。
接下来,我们将“ xy -好区间”中等于 x 的数变成 1,等于 y 的数变成 -1,就可以用上述方法计算双生数组的个数了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 7;
const int inf = LLONG_MAX / 10;
// const int mod = 998244353;
const int mod = 1e9 + 7;
#define x first
#define y second
// 二分暴龍龍
void miaojiachun()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
vector<int>f(n+1);
auto ans=0ll;
for(int i=1;i<=n;i++){
set<int>st;
map<int,int>mp;
mp[0]++;
f[i-1]=0;
for(int j=i;j<=n;j++){
st.insert(a[j]);
if(st.size()>2){
i=j;
st.clear();
for(;i>=1;i--){
st.insert(a[i]);
if(st.size()>2){
break;
}
}
break;
}
f[j]=f[j-1];
if(a[j]==a[i]) f[j]++;
else{
f[j]--;
}
ans+=mp[f[j]];
mp[f[j]]++;
if(j==n) i=n;
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ING = 1;
cin >> ING;
while (ING--)
{
miaojiachun();
}
return 0;
}
that's all.

浙公网安备 33010602011771号