2021牛客寒假算法基础集训营2

D-牛牛与整除分块

规律题
临界点是sqrt(n)
令b=n/x (此题分析从此以下的规律即结论都是举例找出来的)
当x<=sqrt(n)时:b大且分布得比较稀疏(每个b相差好几个数),所以x分别对应一个b,所以此时b是第几大的数?第x大的数。
当x>sqrt(n)时:b小且分别得比较稠密,所以可能几个x对应着同一个b(每个b是在整数意义上连续的),所以此时b是第几大数?第 sqrt(n)+(n/(sqrt(n)+1)-n/x+1) 大的数
以n=25,x=9为例,临界点a=sqrt(25)=5

当x<=5时

#include<bits/stdc++.h>
using namespace std;
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define ll long long
int t;
ll n,x,z;
int main(){
    fio;
    cin >> t;
    while(t--){
        cin >> n >> x;
        if(x*x<=n) cout << x << "\n";
        else {
            z=sqrt(n);
            z+=n/(z+1)-n/x+1;
            cout << z << "\n";
        }
    }
    return 0;
} 

C-牛牛与字符串border (找规律的题目,要找临界点,然后分别看大于和小于以及等于的情况)

此题找规律,举例子
因为题目要求完全匹配(一模一样)
所以子字符串要有两个,即 2l==n 是临界点
所以分别举 2
l<n;2l==n;2l>n; 的例子
例如将n=14,l=6;n=16,l=6;n=5,l=3;时的情况一一列出,
可发现最终的字符串有循环节:
1.当 2l>=n 时,循环节 a=n-l
2.当 2
l<n 时,循环节 a=gcd(n,l)
而循环节中用什么字母呢?
因为题目要求修改次数最少
所以使用出现次数最多的字母
注意:此题未给出 t 的取值范围,虽然memset的复杂度为O(n),但TLE了,所以用for手动初始化
补充:memset比用for初始化常数大一些,空间耗费的也多一些;fill的复杂度也是O(n)
参考代码

int const N=1e5+7;
int t,n,l,a;
string str;
int cnt[N][30],maxn[N];  //cnt表示循环节中每个位置26个字母各自出现的次数 //maxn表示循环节中每个位置当前字母出现的最多次数
char ans[N];  //循环节
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int main(){
    fio;
    cin >> t;
    while(t--){
        //memset(cnt,0,sizeof(cnt));
        //memset(ans,0,sizeof(ans));
        //memset(maxn,0,sizeof(maxn));
        cin >> n >> l;
        cin >> str;
        if(2*l>=n) a=n-l;
        else a=gcd(n,l); 
        //初始化 
        for(int i = 0; i < a; i++){
            for(int j = 0; j < 26; j++)
                cnt[i][j]=0;
            maxn[i]=0;
        }

        int z=0;
        for(int i=0;i<n;++i){
            z=i%a;
            cnt[z][str[i]-'a']++;
            if(cnt[z][str[i]-'a']>maxn[z]){
                maxn[z]=cnt[z][str[i]-'a'];
                ans[z]=str[i];
            }
        }
        for(int i=0;i<n;++i){
            cout << ans[i%a];
        }
        cout << "\n";
    }
    return 0;
}

I-牛牛的“质因数”(埃式筛运用)(用lg求其位数)

规律题
f[x]为题中所给的含义
由于题目中有质数,结合欧拉筛
再举例子观察可得:
f[x]=p* 10^k+f[x/p]; //其中p为最小质因子 //如:f[6]=2 *10^1+f[6/2] ; f[15]=3 *10^2+f[15/3];
上式中的k为f[x/p]的位数,例:f[15/3]的位数为两位,所以此时的k为2

再观察题目:将x的所有质因数从小到大拼接
而欧拉筛是用其最小质因子p,所以p在第一位(也有可能像17这样是两位的),所以此时k求出的是后面的位数,有些耗时,可以再优化一下
所以我们可以考虑p为最大的质因数可得:
f[x]=f[x/p] *10^k+p; //因为质因数从小到大拼接且p为最大的质因数,所以此时的p在最后一位(也有可能像17这样是两位的),所以此时的k为p的位数
如:f[6]=f[6/3] *10^1+2; f[15]=f[15/5] *10^2+3;
那怎样用(找出)最大质因子呢?
比较:欧拉筛每个数筛一次(用最小质因子筛)( O(n) )
埃式筛每个数筛多次(最后一次用最大质因子筛掉)( O(nlglgn) )
所以用埃式筛找最大质因子,并在筛法中维护f[x]即可解答此题
且初始边界为 if(x==质数) f[x]=x;

怎样求位数k?(以后统一用方法一):
1.用log10(x)求x的位数,位数=ceil( lg10(x) )
2.令t[x]为x的位数(此方法只针对此题)
t[x]=t[x/p]+t[p]
边界:if(x==质数) t[x]=用函数求其位数

具体请看代码

注(可以不看这段话):欧拉筛也可以做,但不能边筛边维护
因为欧拉筛用最小质因子筛,如维护f[12]时,f[12/2]此时并无值,并且以后也并不会更新
且初始边界为 if(x==质数) f[x]=x;

int const N=4e6+7;
int const mod=1e9+7;
ll n,ans;
ll f[N];
ll pan(int a){
    ll res=1;
    for(int i=1;i<=a;++i){
        res*=10;
    }
    return res;
} 
int main(){
    cin >> n;
    for(int i=2;i<=n;++i){ //埃式筛复杂度为 O(nlglgn) 
        if(f[i]==0){
            f[i]=i;   //若未被前面的筛掉,说明i是一个质数
            for(int j=i+i;j<=n;j+=i){  //筛掉i的倍数
                f[j]=(f[j/i]*pan ( ceil(log10(i)) )%mod+i)%mod;   
            }
        }    
    }
    for(int i=2;i<=n;++i){
        ans=(ans+f[i])%mod;
    }
    cout << ans;
    return 0;
}

F-牛牛与交换排序(reverse-O(n))(deque)

k=第一个乱序的数 的位置 - 本该待在这个位置的数 的位置
求完k后,在判断k是否能把数列变得有序

reverse(a+1,a+n+1)的复杂度为O(n)
所以题目中的反转操作,可以用双端队列deque完成,即每次反转过后就在另一边插入;删除的话同理

int const N=1e5+7;
int n,f,sx,ex;
int a[N];
int main(){
    fio;
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> a[i];
        if(!f&&i!=a[i]){
            sx=i;f=1;
        }
        if(f&&sx==a[i]){
            ex=i;
        }
    }
    f=0;
    int k=ex-sx+1;
    reverse(a+sx,a+ex+1);
    for(int i=sx;i<=n;++i){
        if(i!=a[i]){
            if(i+k>n+1){
                f=1;break;
            }
            reverse(a+i,a+i+k);
            if(i!=a[i]){ 
                f=1;break;
            }
        }
    }
    if(f) cout << "no";
    else cout << "yes" << "\n" << k;
    return 0;
} 

E-牛牛与跷跷板(前向星的bfs)

题目求最小的跳跃次数,果断可以想到bfs
又相邻的木板之间才可搜(才连通)
那么可以把它抽象成图,相邻的木板连边长为1的边 //若两两看是否相邻会超时,所以先排序,再用了双指针优化了
此时既可以用最短路也可以用bfs(下面用的是bfs,为了使代码短点)

结论:bfs的题目一般都可以用图的最短路来求

int const N=1e5+7;
int n;
struct node{
    int a,next,len;
}e[2000007];
int head[N],cnt;
void add(int x,int y,int len){
    e[++cnt].a=y;
    e[cnt].len=len;
    e[cnt].next=head[x];
    head[x]=cnt;
}
struct L{
    int y,l,r,id;
}a[N];
bool cmp(L a,L b){
    if(a.y==b.y) return a.l < b.l;
    return a.y < b.y;
}
int dis[N];
void bfs(int sx,int ex){
    queue<int>q;
    q.push(sx);
    dis[sx]=1;
    while(!q.empty()){
        int u=q.front();
        for(int i=head[u];i;i=e[i].next){
            if(dis[e[i].a]) continue;
            q.push(e[i].a);dis[e[i].a]=dis[u]+1;
            if(dis[n]) break;  //e[i].a==n
        }
        q.pop();
    }
}
int main(){
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> a[i].y >> a[i].l >> a[i].r;
        a[i].id=i;
    }
    sort(a+1,a+n+1,cmp);
    int p=1,q=1;
    for(int i=1;i<=n;++i){
        if(a[i].r==a[i+1].l&&a[i].y==a[i+1].y){
            add(a[i].id,a[i+1].id,1);
            add(a[i+1].id,a[i].id,1);
        }
        while(p<=n&&a[p].y==a[i].y) p++;
        q=max(p,q);
        while(q<=n&&a[q].y==a[p].y) q++;
        for(;p<q;++p){
            if(a[p].l>=a[i].r) break;
            if(a[p].r<=a[i].l) continue;
            add(a[i].id,a[p].id,1);
            add(a[p].id,a[i].id,1);
        }
        if(p>1) p--;
    }
    bfs(1,n);
    cout << dis[n]-1;
    return 0;
}

J-牛牛想要成为hacker

要使其不构成三角形,用fabi数列
当其数据范围为10^5,直接用会超过10^9,甚至会爆long long
所以要找到一个k使其刚好超过 n* n *logn (n最大取10^5)
即前k个用fabi数列,剩下的补一即可

怎样求k?
当n=10^5时
循环次数之和 >= n^2 lgn
即 循环次数之和 >= 16* n^2 //循环次数可用等差数列求得
得 k=33

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int const N=1e5+7;
int n;
ll a[N];
int main(){
    cin >> n;
    a[0]=1;a[1]=2;
    cout << a[1] << " " ;
    for(int i=2;i<=n&&i<=40;++i){
        a[i]=a[i-1]+a[i-2];
        cout << a[i] << " ";
    }
    for(int i=41;i<=n;++i){
        cout << 1 << " ";
    }
    return 0;  
}

G-牛牛与比赛颁奖(离散化的差分)((n+k-1)/k表示n/k向上取整)

和校门外的树类似(连续的修改,区间修改)

int const M=1e5+7;
struct L{
    int pos,num;
}a[M<<1];   //差分数组
int n,m;
int num[M];   //num[i]表示ac了i题的人数 
bool cmp(L a,L b){
    if(a.pos ==b.pos ) return a.num < b.num ;     //确定先后即先减后加,防止出错
    return a.pos < b.pos ;
}
int cnt; 
int main(){
    cin >> n >> m;
    for(int i=1;i<=m;++i){
        int l,r;
        cin >> l >> r ;
        a[++cnt].pos =l;a[cnt].num++ ;
        a[++cnt].pos =r+1;a[cnt].num--;
    }
    sort(a+1,a+cnt+1,cmp);
    int sum=0,maxn=0;
    for(int i=1;i<=cnt;++i){
        sum+=a[i].num ;   //将离散化的差分做前缀和,从而求出原来的值 //即sum表示某队的ac题目的数量
        if(a[i].pos !=a[i+1].pos ){ 
            num[sum]+=a[i+1].pos -a[i].pos ;
            maxn=max(maxn,sum);
        }
    }
    int j=0,y=0,t=0;
    for(int i=maxn;i>=0;--i){
        num[i]+=num[i+1];  //num[i]表示做后缀和,表示ac了i题以上的人数 
        if(j==0&&num[i]>=(n+9)/10) j=i;    // (n+9)/10)表示n/10向上取整
        if(y==0&&num[i]>=(n+3)/4) y=i;
        if(t==0&&num[i]>=(n+1)/2) t=i; 
    }
    j=max(1,j);y=max(1,y);t=max(1,t);
    cout << num[j] << " " << num[y]-num[j] << " " << num[t]-num[y];
    return 0;
}
全部评论

相关推荐

迷茫的大四🐶:你这个拿去投央国企吧,投私企包过不了的
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务