2022牛客寒假算法基础集训营1

2022牛客寒假算法基础集训营1

链接

A- DP

#include<iostream>
using namespace std;
const int N=1e5+100,mod=998244353;
int a[N],f[N][10];
int count(int x)
{
    x%=9;
    if(x==0) return 9;
    return x;
}  
int main()
{
    int n;
    cin>>n;
    
    for(int i=1;i<=n;i++) cin>>a[i];
    
    f[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=9;j++)
        {
            f[i][j]=(f[i][j]+f[i-1][j])%mod;
            f[i][count(a[i]+j)]=(f[i][count(a[i]+j)]+f[i-1][j])%mod;
        }
            
    for(int i=1;i<=9;i++)
        cout<<f[n][i]%mod<<" ";
    return 0;
}

E -贪心

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
    int t;
    cin>>t;
    while(t--)
    {   
        int n,m;
        cin>>n>>m;
        bool f=0;
        long long ans=1;
        if(n<=m) f=1,ans=1;		
        else if(m==1) f=0;
        else
        {
            f=1;
            int len=ceil(1.0*(n-m)/(m-1));    //向上取整
//          int len=(n-m+(m-1)-1)/(m-1);    //向上取整
//             cout<<len<<endl;
            ans+=len*2;
        }

        if(!f) cout<<-1<<endl;
        else cout<<ans<<endl;
    }

    return 0;
}

J - 前缀和+贪心

先将两种小朋友的幸福度分别按从大到小排序,记为A和B数组;
• 那么最优的方案一定是从A和B中各选一个前缀;
• 因此可以求出两个数组的前缀和,然后枚举从A中选了多少人(从 B中选的人数等于总人数减去A中的),利用前缀和𝑂(1)的获得此
时的总幸福度;
• 对于圆圈紧挨着的限制,其实就相当于限制闹腾小朋友最多选𝑛2个,
在上面枚举的时候加入该限制即可;

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
const int N=20050;
long long a[N],b[N],n,w,A,B;
int main()
{
    int t;
    scanf("%d",&t);
    
    while(t--)
    {
        scanf("%d%d%d",&A,&B,&n);
        
        for(int i=1;i<=A;i++) cin>>a[i];
        for(int i=1;i<=B;i++) cin>>b[i];
        
        if(A<(n-n/2))
        {
            cout<<-1<<endl;
            continue;
        }
        
        sort(a+1,a+A+1); reverse(a+1,a+A+1);
        sort(b+1,b+B+1); reverse(b+1,b+B+1);
        
        for(int i=1;i<=A;i++) 
            a[i]+=a[i-1];

        for(int i=1;i<=B;i++) 
            b[i]+=b[i-1];
        
        long long ans=0;
        for(int i=1;i<=n;i++){	//枚举n个人的分配状况
            int k=n-i;
            if(i<=A&&k<=B&&i>=k){	//需要满足所选人数≤有的人数,A类人数≥B类人数
                ans=max(ans,a[i]+b[k]);
            }
        }

        cout<<ans<<endl;
    }
    
    return 0;
}

H-思维技巧

本题没做出来的xdm要提高对数据范围的敏感程度~
• 注意到 n ≤ 1 0 6 n≤ 10^6 n106 a i ≤ 1000 a_i≤ 1000 ai1000,可以看到 a [ i ] a[i] a[i]范围很小,或者说极
限数据下会有大量重复的值出现——映射
,我们想想怎么利用这一点;
• 记cnt[𝑖]表示𝑖出现的次数,枚举(𝑖,𝑗)对儿(共10^6种);
• 不同情况直接相乘; i ! = j , c n t [ i ] ∗ c n t [ j ] i!=j,cnt[i]*cnt[j] i!=j,cnt[i]cnt[j]
• 特殊处理相同情况; i = j , c n t [ i ] + c n t [ i ] ∗ ( c n t [ i ] − 1 ) / 2 i=j,cnt[i]+cnt[i]*(cnt[i]-1)/2 i=j,cnt[i]+cnt[i](cnt[i]1)/2

#include<iostream>
using namespace std;
const int N=2000;
typedef long long LL;
LL cnt[N];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        cnt[x]++;
    }
    
    LL ans=0;
    for(int i=0;i<=1000;i++)
        for(int j=i;j<=1000;j++)
        {
            LL t=0;
            if(i==j) t=cnt[i]+cnt[i]*(cnt[j]-1)/2;
            else t=cnt[i]*cnt[j];
            ans+=t*abs(i+j-1000);
        }

    cout<<ans<<endl;
    
    return 0;
}

F-中位数切分

结论题: k k k ≤ m ≤m m的数字至少需要 k + 1 k+1 k+1 ≥ m ≥m m的数字维护。因为要求最多段,所以一旦遇到小于 m m m的数花最小代价去维护它即可。剩余的不用维护的皆可单独成段。

记数列中≥ 𝒎的数字有𝒄𝒏𝒕𝟏个,< 𝒎的数字有𝒄𝒏𝒕𝟐个,则答案为
𝒄𝒏𝒕𝟏 − 𝒄𝒏𝒕𝟐,该值≤ 𝟎时输出-1。

#include<iostream>
using namespace std;
const int N=1e5+100;
int a[N],n,m,x;
int main()
{
    int t;
    scanf("%d",&t);
    
    while(t--)
    {
        scanf("%d%d",&n,&m);
        
        int cnt1=0,cnt2=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&x);
            if(x>=m) cnt1++;
            else cnt2++;
        }

        int ans=cnt1-cnt2;
        if(ans<=0) cout<<-1<<endl;
        else cout<<ans<<endl;
    }
}

C-模拟

题意简述:给n个语句 每个语句可能会和其前1-3个语句冲突 冲突语句之间至少间隔三个空位。输入n,然后输入n行,每行三个数字。第i行表示第i个语句的冲突情况。第i行的第j个数字表示i语句是否与i-j语句冲突。1为冲突,0为不冲突。可以在任何语句之间插入空语句,问要插入多少个空语句能满足冲突语句之间都间隔至少三个空位。

n最多只有100,那么我们开一个数组a【105】,记录每个语句在满足条件的情况下存放的位置,最后读取a【n】的位置,和不放任何空语句的状态——a【n】=n来比较差值,就是插入的空语句的数量。

显然每个语句放的位置只受前面语句的位置和是否冲突影响,所以可以在读入的时候直接进行处理。拿到一个语句,我们先把它放在上一个语句的后面:a【i】=a【i-1】+1,然后读入,看它和哪个语句存在冲突。如果和i-j的语句冲突,就把它移动一下,也就是a【i】至少要为a【i-j】+4。与多个语句存在冲突,取距离最远的以向下兼容。注意,间隔三个空位是+4,毕竟没间隔就是+1了。

 #include<iostream>
using namespace std;
const int N=1e5+100;
int a[N];    //存放的是第i个数字为满足前面数字,偏离i的距离
//当发现i位置的数字与i-j位置的数字发生冲突时,i位置数字的位置至少为a[i-j]+4,即i数字与i-j数字距离至少相隔3
int main()
{
    int n;
    cin>>n;

    int k;
    for(int i=1;i<=n;i++)
    {
        a[i]=a[i-1]+1;    //正常是放在上一个数字的后面
        for(int j=1;j<=3;j++)
        {
            cin>>k;
            if(k)    //如果与i-j数字冲突
                a[i]=max(a[i-j]+4,a[i]);    
        }
    }
    
    cout<<a[n]-n<<endl;
    return 0;
}

D-数论+打表找规律

欧拉函数𝜙 ( x ) (x) (x) 定义:对于正整数 n n n,欧拉函数是小于等于 n n n的正整数中与 n n n互质的数的数目

定义: h ( x ) = h(x)= h(x)=𝜙 ( x ) / x (x)/x (x)/x
给定 n n n,求 [ 2 , n ] [2,n] [2,n]范围内 h [ x ] h[x] h[x]的最大值和最小值。

打表发现:

  1. 最大值是 ≤ x ≤x x的最大的质数,
  2. 最小值满足规律 2 , 6 , 30 , 210 , 2310 ⋅ ⋅ ⋅ ⋅ 2,6,30,210,2310···· 2,6,30,210,2310
    我们发现这正好是前几个从1开始的前几个质数的连乘积得到序列, 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 2,3,5,7,11,13,17,19,23,29 2,3,5,7,11,13,17,19,23,29连乘可以得到 2 , 6 , 30 , 210 , 2310 , 30030 , 510510 , 9699690 , 223092870 2,6,30,210,2310,30030,510510,9699690,223092870 2,6,30,210,2310,30030,510510,9699690,223092870,所以 1 e 9 1e9 1e9范围的数字根据打表直接得到最小值。
#include<iostream>
using namespace std;
bool isprime(int x)
{
	if(x<2) return 0;
	for(int i=2;i*i<=x;i++)
	if(x%i==0) return false;
	
	return true;
}
int a[]={2,6,30,210,2310,30030,510510,9699690,223092870};
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int x;
        cin>>x;
        if(x==1) 
        {
            cout<<-1<<endl;
            continue;
        }
        int ans1,ans2;
		
		//得到最小值
        if(x<6) ans1=2;
        else if(x<30) ans1=6;
        else if(x<210) ans1=30;
        else if(x<2310) ans1=210;
        else if(x<30030) ans1=2310;        
        else if(x<510510) ans1=30030;
        else if(x<9699690) ans1=510510;        
        else if(x<223092870) ans1=9699690;
        else ans1=223092870;        
        
        //最大值:≤x的最大的质数
        for(int i=x;i>=1;i--)
        if(isprime(i))
        {
        	ans2=i;
        	break;
        }
        
        cout<<ans1<<" "<<ans2<<endl;
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值