2025牛客寒假算法基础集训营1
A
乍一看,找一个数不是数组内所有数的倍数,或者因数
但是换个角度想,如果这个数的因数,都没有这些数,且这个数比数组内的所有数都大
那么很容易想到,1e9+7,直接输出即可
B
一棵树上,不重不漏地经过每一个点,那么这样一棵树必然是一条链
所以只用考虑每个节点的“度”
D
判断数组内是否只出现两个数且数量相等,按照题意模拟即可
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
map<int,int>mp;
void solve(){
cin>>n;
int cnt=0;mp.clear();
for(int i=1;i<=n;++i){
int x;
cin>>x;
if(mp[x]==0) ++cnt,mp[x]++;
else mp[x]++;
}
if(cnt!=2){
puts("No");
}
else {
int last=0;
for(auto [x,y]:mp){
if(last==0) last=y;
else{
if(last!=y) puts("No");
else puts("Yes");
}
}
}
return ;
}
int main(){
cin>>t;
while(t--){
solve();
}
return 0;
}
G
可以对数组内的数,选一个+1,选一个-1,其实就是转移值罢了
所以如果数组内的数的和不等于一个排列的总和,那就不能形成一个排列
贪心地想,每个数变成离他最近的(1-n)之间的数
先排序,那么答案
ans/=2,因为是转移值
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n;
int top=0;
const int maxn=1e5+10;
ll sum=0;
int a[maxn];
int f[maxn];
bool book[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
sum+=a[i];
if(a[i]<=n && a[i]>=1 && !book[a[i]])book[a[i]]=1;
else f[++top]=a[i];
}
if(sum!=(ll)n*(1+n)/2){
puts("-1");
}
else{
ll ans=0;
int j=1; sort(f+1,f+1+top);
for(int i=1;i<=top;++i){
while(book[j]==1 && j<=n) ++j;
ans+=abs(j-f[i]);
}
cout<<ans/2<<endl;
}
return 0;
}
E
中位数定理:
当m取数组a中位数时,取最小值
反证法,假如此时最小,设
若,那么
同理,
综上 中位数定理成立
对于此题来讲,相当于将数组分为两段,两段都变成中位数时,所求操作数最小
特殊情况:当两段中位数相等时,应该考虑,左段中位数-1或者右段中位数+1后,操作的最小数
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
const int maxn=1e5+10;
int a[maxn];
int main(){
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
sort(a+1,a+1+n);
int l=1,r=n,mid=(l+r)>>1;
int x=a[(l+mid)>>1],y=a[(mid+1+r)>>1];
long long ans=1e15;
if(x==y){
y+=1;
long long cnt=0;
for(int i=1;i<=mid;++i) cnt+=abs(a[i]-x);
for(int i=mid+1;i<=n;++i) cnt+=abs(a[i]-y);
--y;--x;ans=min(ans,cnt);
}
long long cnt=0;
for(int i=1;i<=mid;++i) cnt+=abs(a[i]-x);
for(int i=mid+1;i<=n;++i)cnt+=abs(a[i]-y);
ans=min(ans,cnt);
cout<<ans<<endl;
}
return 0;
}
M
贪心地想,我们将最小值翻倍,然后算极差,但是可能最小值翻倍后比最大值还大,那么我们就需要考虑翻倍次小值
以此类推,从最小值开始,然后将扩张的区间扩张到次小值,最后到最大值
这样的扩张每个元素只会计算一次,时间复杂度O(n)
扩张的时候用set维护,当前数组的翻倍的区间,和未翻倍的区间,在每次扩张到一个最小值时,计算极差
#include <bits/stdc++.h>
using namespace std;
const int maxn= 1e5 + 5;
typedef long long ll;
struct node{
ll val,p;
}f[maxn];
bool cmp(node x,node y){
return x.val<y.val;
}
void solve()
{
int n;
cin>>n;
set<ll>st1,st2;
vector<ll>a(n+1);
for(int i=1;i<=n;i++){
cin>>f[i].val;
f[i].p=i;
a[i]=f[i].val;
st2.insert(f[i].val);
}
sort(f+1,f+1+n,cmp);
if(n==1){
cout<<0<<endl;
return ;
}
ll maxx=max(f[n].val,f[1].val*2),minn=1e15;
ll ans=maxx-min(f[1].val*2,f[2].val);
st1.insert(f[1].val*2);
st2.erase(f[1].val);
int l=f[1].p,r=f[1].p;
for(int i=2;i<=n;i++){
while(f[i].p>r){
r++;
st1.insert(a[r]*2);
st2.erase(a[r]);
}
while(f[i].p<l){
l--;
st1.insert(a[l]*2);
st2.erase(a[l]);
}
if(st1.size()&&st2.size()){
maxx=max(*st1.rbegin(),*st2.rbegin());
minn=min(*st1.begin(),*st2.begin());
ans=min(ans,maxx-minn);
}
else ans=min(ans,*st1.rbegin()-*st1.begin());
}
cout<<ans;
}
int main()
{
cin.tie(0);
cout.tie(0);
int t = 1;
while (t--)
solve();
return 0;
}
C
(2025.2.4更新)
将矩阵中的1全部移到左上部,操作次数不多于
暴力不会超出次数,最多总共移动个元素,每个元素直接行列之间,最多交换个,所以操作次数不多于
然后左上矩阵每个位置,是0就找1交换,是1就跳过
交换时模拟,记下路径
注意一点:左上矩阵中的0(列m),和找到的1(列n),如果m>n,就先交换列,再交换行,反之,先交换行,再交换列
举个例子
111010
100000
000000
111001
我们此时换的是(2,2)的0,最近的1是(1,5),如果我们先交换列,再交换行
结果
101100
110000
000000
111001
导致错误
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> pii;
int t;
int n;
const int maxn=1e2+10;
bool mp[maxn][maxn];
bool vis[maxn][maxn];
vector<pii>s,z;
int dis(pii a,pii b){
return abs(b.x-a.x)+abs(b.y-a.y);
}
void move1(int sx,int sy,int ex,int ey){//先行后列
while(sx!=ex){
int nx=sx+(sx>ex?-1:1);
swap(mp[sx][sy],mp[nx][sy]);
s.push_back({sx,sy});
z.push_back({nx,sy});
sx=nx;
}
while(sy!=ey){
int ny=sy+(sy>ey?-1:1);
swap(mp[sx][sy],mp[ex][ny]);
s.push_back({sx,sy});
z.push_back({sx,ny});
sy=ny;
}
}
void move2(int sx,int sy,int ex,int ey){//先列后行
while(sy!=ey){
int ny=sy+(sy>ey?-1:1);
swap(mp[sx][sy],mp[sx][ny]);
s.push_back({sx,sy});
z.push_back({sx,ny});
sy=ny;
}while(sx!=ex){
int nx=sx+(sx>ex?-1:1);
swap(mp[sx][sy],mp[nx][sy]);
s.push_back({sx,sy});
z.push_back({nx,sy});
sx=nx;
}
}
void bfs(pii p){
pii ans={110,110};
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
if(vis[i][j]) continue;
if(dis(p,{i,j})<dis(p,ans) && mp[i][j]==1) ans={i,j};
}
if(ans.y<p.y) move2(ans.x,ans.y,p.x,p.y);//先列后行
else move1(ans.x,ans.y,p.x,p.y);//先行后列
}
void solve(){
cin>>n;
s.clear();z.clear();
memset(vis,0,sizeof vis);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
char c;cin>>c;
mp[i][j]=(c=='1')?1:0;
}
for(int i=1;i<=n/2;++i)
for(int j=1;j<=n/2;++j){
vis[i][j]=1;
if(mp[i][j]) continue;
bfs({i,j});
}
cout<<s.size()<<"\n";
for(int i=0;i<s.size();++i){
cout<<s[i].x<<" "<<s[i].y<<" "<<z[i].x<<" "<<z[i].y<<"\n";
}
}
int main(){
cin>>t;
while(t--){
solve();
}
return 0;
}
J
求满足数组中满足的对数
- 法一
令假设y>x
那么
而一定是x,y的一个因数,而根据推出来的表达式,我们只需要找一个x的因数t检验表达式是否成立即可
一旦检验成功,对答案的贡献就是x,t出现的次数相乘
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n;
const int maxn=2e6+10;
int a[maxn];
int mp[maxn+10];
int gcd(int a,int b){
while(b){
int t=a%b;
a=b;
b=t;
}
return a;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
ll ans=0;
sort(a+1,a+1+n);
for(int i=1;i<=n;++i){
mp[a[i]]++;
for(int j=1;j*j<=a[i];++j){
if(a[i]%j==0){
int f=a[i]/j;
ll cnt=0;
if(gcd(a[i],a[i]^f)==f)
cnt+=mp[a[i]^f];
if(gcd(a[i],a[i]^j)==j)
cnt+=mp[a[i]^j];
if(f==j) cnt/=2;
ans+=cnt;
}
}
}
cout<<ans<<endl;
return 0;
}
- 法二
打表,找规律
摘自牛客(侵删):
作者:世界第一可爱的嘤嘤嘤
链接:https://ac.nowcoder.com/discuss/1452662?type=0&channel=-1&source_id=discuss_terminal_discuss_hot_nctrack
来源:牛客网
我们发现
x,y(x<y) 满足 x⊕y=gcd(x,y) ,当且仅当 y=x+gcd(x,y) ,且 x 是偶数
事实上无需判断
x奇数还是偶数,只是满足要求的恰好 都为偶数,编程中不判断这点也可以 等价于
1.证明 二进制位数相等 假设不相等,那么 一定大于
因为异或的位数等于 中位数多的那个. 则 ,与 矛盾
2.在 二进制位数相等且 的基础上,显然
H
学到了,经典贪心+构造
如果i可以有多种区间选择,那么选择右区间最小的一定不为最差解
例如:
[l,r+5],[l,r]选择第二个区间,一定满足题意
所以对于该题来讲,我们尽量使每一个数,在靠近它所在区间最左边的位置,就是最优解
因为这样总是能为后续的数腾出空间
当我们考虑第i个位置时
- 如果此时区间右端点r<i,应该舍去,但是此题不能舍去,因此无解
- 如果我们没有可以选择的区间,同样无解
先按照区间左端点排序,考虑每个位置的数时,用优先队列按照右端点大小排序,选择最小的右端点区间
#include<bits/stdc++.h>
using namespace std;
#define r first
#define id second
typedef pair<int,int> pii;
priority_queue<pii >q;
int n;
const int maxn=1e5+10;
struct node{
int l,r;
int num;
} a[maxn];
int ans[maxn];
bool cmp(node x,node y){
return x.l<y.l;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i].l>>a[i].r,a[i].num=i;
int now=1,i=1;
sort(a+1,a+1+n,cmp);
while(now<=n){
while(i<=n && a[i].l==now){
q.push({-a[i].r,a[i].num});
++i;
}
if(!q.empty()){
auto t=q.top();q.pop();
if(-t.r>=now) ans[t.id]=now++;
else{
puts("-1");
return 0;
}
}
else{
puts("-1");
return 0;
}
}
for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
return 0;
}
F
(2025.2.5更)
条件:一个区间只含两种数且数量相等
我们首先可以思考知道,一定会有一段满足该条件最长的连续区间,包含很多小区间也满足该条件
- 所以如何去统计数量就是一个问题:
假设此时我们有一个最长的区间[l,r],我们可以从左到右,依次统计两种数的数量
当发现两种数相同时,可以为答案作出贡献,但是此时做出贡献的区间只是以l为左端点的区间
- 接下来我们要思考,如何统计不以l为左端点的区间:
例如,验证是否满足:
反向推导,如果满足,那么中两种数相等
我们设中两种数的数量差是x,那么中两种数的数量差也是x
- 因此,我们可以设等于第一种数+1,等于第二种数-1
依次统计数量差f,统计到r,如果,前面出现过多少个这样的l,对答案贡献就有多少个
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
const int maxn = 1e5 + 10;
int a[maxn];
void solve() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; ++i) {
int j = i;
set<int> s; // 用于存储当前子数组中出现的不同元素
s.insert(a[i]);
// 扩展子数组的右端点,直到无法满足条件为止
while (j + 1 <= n && (s.size() < 2 || s.count(a[j + 1]))) {
s.insert(a[++j]); // 将下一个元素加入集合
}
if (s.size() < 2) break; // 如果集合中的元素少于两种,则退出循环
int cnt = 0; // 用于记录两种元素的数量差
map<int, int> mp; // 用于记录前缀差的出现次数
mp[0] = 1; // 初始前缀差为0,出现次数为1
// 统计当前子数组区间的方案数
for (int k = i; k <= j; ++k) {
cnt += (a[k] == a[i]) ? 1 : -1; // 更新当前的差值
ans += mp[cnt]; // 累加符合条件的子数组数目
mp[cnt]++; // 更新前缀差的出现次数
}
// 跳过连续相同的元素,优化性能
int m = j;
while (m - 1 >= i && a[j] == a[m - 1]) --m;
i = m - 1; // 跳转到连续相同元素的前面
}
cout << ans << endl;
return;
}
int main() {
cin >> t;
while (t--) {
solve();
}
return 0;
}
本文作者:归游
本文链接:https://www.cnblogs.com/guiyou/p/18688718
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步