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

A

九小时九个人九扇门

题意:给你n个数,从中选出一些数字,使得数字和的数字根分别为1~9的组合分别有多少种

结论:一个数的数字根等于这个数对9取模的结果(特别地,取模得 0则数字根为9)

 问题转化为:从{𝑎𝑛}中选择一些数字使得其求和对9取模得0,1,2,3,4,5,6,7,8有多少种选法;

思路:这是一个经典的0/1背包变形的DP问题;令𝑑𝑝[𝑖][𝑗]表示考虑了前𝑖个数,选择了一些数字使得求和对9取模得𝑗的方案数;

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e5 + 10;
const int mod=998244353;
int n;
int dp[maxx][10];
void solve()
{
    scanf("%d",&n);
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        x%=9;
        for(int j=0;j<=8;j++)
        {
            dp[i][(j+x)%9]=(dp[i][(j+x)%9]+dp[i-1][j])%mod;
            dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
        }
    }
    for(int i=1;i<=9;i++)
    {
        if(i==9) printf("%d",dp[n][0]-1);
        else printf("%d ",dp[n][i]);
    }
}
int main()
{
    //scanf("%lld", &t);
    //while (t--)
    //{
        solve();
    //}
    system("pause");
}

C

Baby's first attempt on CPU

题意:两条读写语句间隔少于三句会发生读写问题,可以在两条读写语句中插入空语句解决问题,给定原程序,求最少插入空语句的操作次数

思路:开一个数组pos记录原程序语句在新程序中的位置,遍历原程序,用t表示在新程序的第t行,发生冲突则t++增加空语句,遍历完第i句的冲突之后,插入到新程序第t句,更新pos[i]

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e6 + 10;
int t, n;
int a[110][5];
int pos[10000];//pos[i]=x表示原程序第i句在新程序中是第x句
void solve()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        t++;//t表示新程序中第t行
        for(int j=1;j<=3;j++)
        {
            scanf("%d",&a[i][j]);
            if(a[i][j])
            {
                while(t-pos[i-j]-1<3)
                {
                    t++;
                }
            }
        }
        pos[i]=t;//遍历完第i句的冲突之后,插入到新程序第t句
    }
    printf("%d",t-n);
}
int main()
{
    //scanf("%lld", &t);
    //while (t--)
    //{
        solve();
    //}
    system("pause");
}

E

炸鸡块君的高中回忆

题意:n个人进学校,m个人带了校园卡,先让m个人带校园卡进入学校,再派一个人带着所有m张校园卡出来,重复上述过程,直到所有人进入学校。进出都需要花费一个单位时间,问共花费多少时间

思路:首先n>1&&m==1时无法全部进入,若n<=m,则只需花费1分钟,当n>m时,只能分多批进入。一批相当于只进入了m-1个人,进行i轮进m出1后,还剩下n-i*(m-1)人,当剩下的人数小于等于m时,即i>=(m-n)/(1-m),(注意,i为轮数需要向上取整)   最后一批(花费1时间)可以全部进入。共花费2*i+1时间.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e6 + 10;
ll t, n, m;
void solve()
{
    scanf("%lld %lld", &n, &m);
    if (n > 1 && m == 1)
        printf("-1\n");
    else
    {
        if (n <= m)
            printf("1\n");
        else if (n > m)
        {
            ll i=(double)(1.0*m-n)/(1.0-m);
            if((m-n)%(1-m)!=0) i++;//向上取整
            ll ans=i*2+1;
            printf("%lld\n",ans);
        }
    }
}
int main()
{
    scanf("%lld", &t);
    while (t--)
    {
        solve();
    }
    system("pause");
}

F

中位数切分

题意:给定一个长为n的数组a和一个整数m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于m,求最多可以划分成多少段。

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

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e5 + 10;
int n,m,t;
void solve()
{
    scanf("%d %d",&n,&m);
    int cnt1=0,cnt2=0;
    int tem;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&tem);
        if(tem>=m) cnt1++;
        else cnt2++;
    }
    if(cnt1-cnt2<=0) printf("-1\n");
    else printf("%d\n",cnt1-cnt2);
}
int main()
{
    scanf("%d", &t);
    while (t--)
    {
        solve();
    }
    system("pause");
}

  

J

小朋友做游戏

题意:A个安静的小朋友和B个闹腾的小朋友,选n个小朋友围成圈做游戏,但闹腾小朋友不能相邻,给定每个小朋友的幸福度,求出最大幸福度

思路:先将两组幸福度进行降序排序分别叫做arr和brr数组。如果选定安静小朋友人数<选定的闹腾小朋友的人数则不能进行游戏,否则最优方案一定是从arr和brr中各取一个前缀和。因此可以求出两个数组的前缀和,然后枚举从A组中选了多少人(从 B中选的人数等于总人数n减去A中的),利用前缀和𝑂(1)的获得此时的总幸福度。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e4 + 10;
ll t,n,A,B;
ll arr[maxx],brr[maxx];
ll sa[maxx],sb[maxx];
bool cmp(ll a,ll b)
{
    return a>b;
}
void solve()
{
    ll ret=0,ans=-0x3f3f3f3f;
    scanf("%lld %lld %lld",&A,&B,&n);
    for(ll i=1;i<=A;i++) scanf("%lld",&arr[i]);
    for(ll i=1;i<=B;i++) scanf("%lld",&brr[i]);
    sort(arr+1,arr+A+1,cmp);
    sort(brr+1,brr+B+1,cmp);
    for(ll i=1;i<=A;i++) sa[i]=sa[i-1]+arr[i];
    for(ll i=1;i<=B;i++) sb[i]=sb[i-1]+brr[i];
    ll tem=0;
    if(n%2==0) tem=n/2;//i>=n-i控制i的下界
    else tem=n/2+1;//n为奇数时会向下取整,所以要+1
    for(int i=min(A,n);i>=tem;i--)//注意i从A和n的最小值开始枚举
    {
        if(n-i>B||n-i>i) continue;//选中B组人数n-i大于B组总人数||闹腾人数>安静人数
        ret=sa[i]+sb[n-i];
        ans=max(ans,ret);
    }
    if(ans<0) printf("-1\n");
    else printf("%lld\n",ans);

}
int main()
{
    scanf("%lld",&t);
    while (t--)
    {
        solve();
    }
    system("pause");
}

H

牛牛看云

题意:

注意到n<=1e6,0<=ai<=1000的,所以会有大量的值重复出现

观察发现,答案与给定序列ai的顺序无关,因为是两层for循环。

思路:开一个cnt数组记录元素出现的次数,然后枚举所有的<i,j>对儿

          i!=j时,对答案的贡献为cnt[i]*cnt[j]*abs(i+j-1000)

          注意i=j时,对答案的贡献为(cnt[i]+C_{cnt[i]}^{2})*abs(i+j-1000)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxx = 1e6 + 10;
int n;
ll ans;
ll a[maxx];
ll cnt[1010];//cnt[i]为元素i出现的次数
int main()
{
    ll add=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]),cnt[a[i]]++;
    for(ll i=0;i<=1000;i++)
    {
        for(ll j=i;j<=1000;j++) //枚举i,j对儿,j从i开始枚举
        {
            if(i==j) add=(cnt[i]+cnt[i]*(cnt[i]-1ll)/2ll);//自己跟自己配对+与别人配对
            else add=(ll)cnt[i]*cnt[j];
            ans+=add*(ll)abs(i+j-1000);
        }
    }
    printf("%lld",ans);
    system("pause");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值