A-茕茕孑立之影
思路:由于 1 是任何数的因数,所以如果数组中有 1 的话直接输出 -1 即可;如果没有 1 ,我们知道 ai 不可能是质数的因数,所以我们肯定要找一个质数,然后又要使得这个质数不能是 ai 的因数,那么所找的质数 x 需要比所有 ai 都大。时间复杂度 O(n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
int t;
bool isp(int n)//判断质数
{
if (n <= 1)
return 0;
if (n == 2 || n == 3)
return 1;
if (n % 2 == 0 || n % 3 == 0)
return 0;
int sqn = sqrt(n);
for (int i = 5; i <= sqn; i += 6)
{
if (n % i == 0 || n % (i + 2) == 0)
return 0;
}
return 1;
}
void solve()
{
int n;
cin >> n;
bool flag = 0;
int maxn = 0;
rep(i, 1, n)
{
int a;
cin >> a;
maxn = max(a, maxn);
if(a==1)
flag = 1;
}
if(flag)
{
cout << -1 << endl;
return;
}
int i = maxn + 1;
while(!isp(i))
i++;
cout << i << endl;
}
int main()
{
IOS;
cin >> t;
while(t--)
solve();
return 0;
}
B-一气贯通之刃
思路:因为在一棵树中任意两点间有且只有一条简单路径,如果存在一个点的度数大于 2 那么一条路径肯定会有走不到的边。所以,在一棵树中,如果存在一条简单路径,使得这条路径不重不漏地经过所有结点,那么这棵树一定是一条链。一条链有且仅有 2 个度数为 1 的端点,剩下的度数都为 2 ,根据这个特点判断这棵树是否是链,然后输出端点即可。时间复杂度 O(n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
vector<int> d;//度数
int main()
{
IOS;
int n;
cin >> n;
d.resize(n + 1);
rep(i,1,n-1)
{
int u, v;
cin >> u >> v;
d[u]++;
d[v]++;
}
int cnt = 0;//度数为 1 的点的数量
int p1 = 0, p2 = 0;
rep(i,1,n)
{
if (d[i] == 1)
{
cnt++;
if (cnt == 1)
p1 = i;
else if(cnt == 2)
p2 = i;
}
else if (d[i] != 2)
{
cout << -1 << endl;
return 0;
}
}
if (cnt != 2)
cout << -1 << endl;
else
cout << p1 << " " << p2 << endl;
return 0;
}
D-双生双宿之决
思路:模拟即可。时间复杂度 O(n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
int t;
void solve()
{
int n;
cin >> n;
unordered_map<int,int>vs;
int fro;
rep(i,1,n)
{
int a;
cin >> a;
vs[a]++;
if(i==1)
fro=a;
}
if(n%2!=0||vs.size()!=2)
{
cout << "No" << endl;
return;
}
for(auto &u:vs)
{
if(u.second!=vs[fro])
{
cout << "No" << endl;
return;
}
else
{
cout << "Yes" << endl;
return;
}
}
}
int main()
{
IOS;
cin >> t;
while(t--)
solve();
return 0;
}
E-双生双宿之错
思路:我们发现,如果每次操作都对一个数组的某个元素加 1 或者减 1 ,直到数组的所有元素都变得相同,那么最小的操作次数就是将所有元素都变成数组的中位数。于是我们可以将数组排序,分成前后两个半段,然后求出前后两个半段的中位数,根据以上结论求出最小操作次数即可。需要注意的是,如果前后两段的中位数相等,那么我们可以分别求出前段的中位数减 1 后,和后段的中位数加 1 后的操作次数,比较得出最小的结果。时间复杂度 O(n log n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
vector<LL>a;
LL cnt(LL m1, LL m2, int n)//计算操作次数
{
LL res=0;
rep(i,1,n/2)
res+=abs(a[i]-m1);
rep(i,n/2+1,n)
res+=abs(a[i]-m2);
return res;
}
int main()
{
IOS;
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
a.resize(n+1);
rep(i,1,n)
cin >> a[i];
sort(a.begin()+1,a.begin()+n+1);
LL m1,m2;//前后半段的中位数
if((n/2)%2==0)
m1=(a[(1+n/2)/2]+a[(1+n/2)/2+1])/2,m2=(a[(n/2+1+n)/2]+a[(n/2+1+n)/2+1])/2;
else
m1=a[(1+n/2)/2],m2=a[(n/2+1+n)/2];
if(m1==m2)
{
m1--;
LL ans1=cnt(m1,m2,n);
m1++,m2++;
LL ans2=cnt(m1,m2,n);
cout << min(ans1,ans2) << endl;
}
else
cout << cnt(m1,m2,n) << endl;
}
return 0;
}
G-井然有序之衡
思路:我们发现题意的操作对数组和的贡献其实是 0 ,所以只要数组和等于 n 的排列和就可以变成排列。由于需要求的是最小操作次数,我们贪心地思考,把数组中最小的数变成 1 ,倒数第二的变成 2 ......以此类推。我们可以将数组排序,求每个数与目标数的差距,发现由于操作后数组和不会变,那么总的正差距一定等于总的负差距,所以我们每次操作都要缩小一个正差距和负差距,输出正差距即为最小操作次数。时间复杂度 O(n log n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
LL a[100005];
int main()
{
IOS;
int n;
cin >> n;
LL sum = 0;
rep(i,1,n)
{
cin >> a[i];
sum += a[i];
}
if(sum!=((LL)(1+n)*(LL)n)/2)//数组和 sum 必须等于 n 的排列和
{
cout << -1;
return 0;
}
sort(a + 1, a + 1 + n);
LL po = 0;
rep(i,1,n)
{
LL d = a[i] - i;
if(d>=0)
po += d;
}
cout << po;
return 0;
}
H-井然有序之窗
思路:使用贪心策略。从 1 到 n 按顺序选择区间指定 i 时,我们发现,在包括 i 的前提下,我们应该尽可能选择右端点较小的区间,如右端点相等则选择左端点更小的区间(即更靠左的区间),由于 i 是在递增的,这样的区间紧迫性更强,优先处理尽量避免后续区间无法找到合适的数字,如果找不到合适的区间则输出 -1 。具体地,我们使用优先队列来维护右端点最小的区间,每次只将左端点等于 i 的区间放入优先队列,然后将 i 指定给最合适的区间并弹出。时间复杂度 O(n log n) 可以通过此题。
#include <bits/stdc++.h>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
vector<vector<pair<int, int> > >qu;
priority_queue<pair<int, int>, vector<pair<int, int> >,greater<pair<int, int> > >pq;
vector<int>ans;
int main()
{
IOS;
int n;
cin >> n;
qu.resize(n);
rep(i,1,n)
{
int l,r;
cin >> l >> r;
qu[l].push_back({r,i});
}
ans.resize(n);
rep(i,1,n)
{
for(auto &u:qu[i])
pq.push(u);
if(pq.empty()||pq.top().first<i)
{
cout << -1;
return 0;
}
ans[pq.top().second-1]=i;
pq.pop();
}
for(auto &u:ans)
cout << u << " ";
return 0;
}
J-硝基甲苯之袭
思路:我们设所取的两个数为 x 和 y,令 p 等于 x^y,根据异或运算的性质,x^p 就会等于 y ,于是我们只需判断 p == gcd(x, x^p) 就能知道 x 与 y 是否为所求,又两个数的 gcd 肯定是其中之一的因数,所以我们只需枚举数组每个数的因数,判断组合是否为所求,然后记录组合个数即可。时间复杂度 O(n sqrt(n) log n) 可以通过此题。
#include <bits/stdc++.h>
#include <numeric>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
int a[200005];
unordered_map<int, int> vs;
map<pair<int,int>, int >mp;
int gcd(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
int main()
{
IOS;
int n;
cin >> n;
rep(i, 1, n)
{
cin >> a[i];
vs[a[i]]++;
}
LL cnt = 0;
rep(i, 1, n)
{
int sqn = sqrt(a[i]);
rep(j, 1, sqn)
{
if (a[i] % j == 0)
{
int p1 = j, p2 = a[i] / j;
int y1 = a[i] ^ p1, y2 = a[i] ^ p2;
if (p1 == gcd(a[i], y1) && vs[y1] != 0 && y1 != a[i] && mp[{a[i], y1}] == 0 && mp[{y1, a[i]}] == 0)
{
cnt+=vs[a[i]]*vs[y1];
mp[{a[i], y1}] = 1, mp[{y1, a[i]}] = 1;
}
if (p2 == gcd(a[i], y2) && vs[y2] != 0 && y2 != a[i] && mp[{a[i], y2}] == 0 && mp[{y2, a[i]}] == 0)
{
cnt+=vs[a[i]]*vs[y2];
mp[{a[i], y2}] = 1, mp[{y2, a[i]}] = 1;
}
}
}
}
cout << cnt;
return 0;
}
M-数值膨胀之美
思路:贪心的思想,由于极差由最小值与最大值决定,所以我们尽量翻倍较小值。可以维护一个覆盖前 i 小值的区间 [l, r](区间初始只覆盖最小值,逐渐扩展覆盖到其他值),在区间扩展到覆盖第 i 小值的过程中记录数组最大值和最小值变化,从而计算出最小的极差。具体地,我们可以通过将数组 a 排序成 sa,来轻易地得出前 i 小值,每次扩展后的数组最小值不是第一小值 sa[1] 的两倍,就是下一个要拓展的值 sa[i+1] ,最大值是区间内每个数乘 2 后的最大值。时间复杂度 O(n log n) 可以通过此题。
#include <bits/stdc++.h>
#include <numeric>
#define endl '\n'
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, n, a) for (int i = n; i >= a; i--)
#define LL long long
#define IOS \
ios::sync_with_stdio(0); \
cin.tie(0); \
cout.tie(0);
using namespace std;
LL a[100005];
vector<pair<int,int>>sa;
int main()
{
IOS;
int n;
cin >> n;
sa.resize(n+2);
rep(i,1,n)
{
cin >> a[i];
sa[i].first=a[i],sa[i].second=i;
}
sa[n+1].first=2e9;
sort(sa.begin()+1,sa.begin()+1+n);
int l=sa[1].second,r=sa[1].second;
LL maxn=max(sa[1].first*2,sa[n].first);
LL ans=maxn-min(sa[1].first*2,sa[2].first);
rep(i,2,n)
{
while(sa[i].second<l)
{
l--;
maxn=max(maxn,a[l]*2);
}
while(sa[i].second>r)
{
r++;
maxn=max(maxn,a[r]*2);
}
ans=min(ans,maxn-min(sa[1].first*2,sa[i+1].first));
}
cout << ans;
return 0;
}
牛客集训营算法题解精讲
2094

被折叠的 条评论
为什么被折叠?



