为了能到远方,脚下的每一步都不能少.|

园龄:粉丝:关注:

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=sumi=1n|aii|

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中位数时,f(m)=i=1n|aim|取最小值

反证法,假如此时最小,设mid=l+r2m=a[mid]

m=a[mid+1],那么f(m)=i=1n|aim|=f(m)+(mid(n(mid+1)))=f(m)+1>f(m)
同理m=mid+x,f(m)=f(m)+|x|>f(m)

综上 中位数定理成立

对于此题来讲,相当于将数组分为两段,两段都变成中位数时,所求操作数最小

特殊情况:当两段中位数相等时,应该考虑,左段中位数-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全部移到左上部[i,j](1i,jn2),操作次数不多于n32

暴力不会超出次数,最多总共移动n2/4个元素,每个元素直接行列之间,最多交换2n个,所以操作次数不多于n32

然后左上矩阵每个位置,是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

求满足数组中满足gcd(x,y)=xy的对数

  • 法一
    t=gcd(x,y)=xy假设y>x
    那么y=txgcd(x,tx)=t
    t一定是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奇数还是偶数,只是满足要求的恰好 x 都为偶数,编程中不判断这点也可以 gcd(x,y)=xy(y>x) 等价于 gcd(x,y)=yx
1.证明 x,y 二进制位数相等 假设不相等,那么 xy 一定大于 min(x,y)
因为异或的位数等于 x,y 中位数多的那个. 则 gcd(x,y)<=min(x,y)<xy ,与 gcd(x,y)=xy 矛盾
2.在 x,y 二进制位数相等且 y>x 的基础上,显然 xy=yx

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更)

条件:一个区间只含两种数且数量相等

我们首先可以思考知道,一定会有一段满足该条件最长的连续区间,包含很多小区间也满足该条件

  1. 所以如何去统计数量就是一个问题:

假设此时我们有一个最长的区间[l,r],我们可以从左到右,依次统计两种数的数量

当发现两种数相同时,可以为答案作出贡献,但是此时做出贡献的区间只是以l为左端点的区间

  1. 接下来我们要思考,如何统计不以l为左端点的区间:

例如,验证[lr](l>l)是否满足:

反向推导,如果满足,那么[l,r]中两种数相等

我们设[1,l]中两种数的数量差是x,那么[1,r]中两种数的数量差也是x

  1. 因此,我们可以设等于第一种数+1,等于第二种数-1

依次统计数量差f,统计到r,如果fr=fl,前面出现过多少个这样的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 中国大陆许可协议进行许可。

posted @   归游  阅读(17)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起