首页 > 编程语言 >2023牛客寒假算法基础集训营6

2023牛客寒假算法基础集训营6

时间:2023-02-04 14:12:28浏览次数:61  
标签:cnt 集训营 int ans long 牛客 maxn 2023 now

2023牛客寒假算法基础集训营6

A

A

直接判断

#include <bits/stdc++.h>
using namespace std;
#define int long long 
int x;
signed main ()
{
    cin>>x;
    if (x<=7)
    {
        cout<<"very easy\n";
    }
    else if (x<=233)
    {
        cout<<"easy\n";
    }
    else if (x<=10032)
    {
        cout<<"medium\n";
    }
    else if (x<=114514)
    {
        cout<<"hard\n";
    }
    else if (x<=1919810)
    {
        cout<<"very hard\n";
    }
    else 
    {
        cout<<"can not imagine\n";
    }
    return 0;
}

B

B

题目大意是给你n个数,我们有两种操作,一是在数组的最后面加上x,还有一种是查询x后面有多少的数是ax的倍数

当时想的是我们可以记录在ai时记录从1到ai的以a[i]为因子的数,放进sum数组里面

然后我们查询的时候我们就可以拿上以ax为因子的数的数量,但是我们此时得到的是所有的数,但是题目要求是x后面的数量,那么我们减去前面以ax为因子的数(这个数一定是ax的倍数),这个我们记录在sum里

有点像前缀和

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=2e5+10;
int n,q;
int a[maxn<<1];
vector<int>factor[maxn];
int sum[maxn<<1];
int cnt[maxn];
void init()
{
    for (int i=1;i<=2e5;i++)
    {
        for (int j=i;j<=2e5;j+=i)
        {
            factor[j].push_back(i);
        }
    }
    return ;
}
signed main ()
{
    init();
    cin>>n>>q;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
        for (auto d:factor[a[i]])
        {
            cnt[d]++;
        }
        sum[i]=cnt[a[i]];
    }
    while (q--)
    {
        int op,x;
        cin>>op>>x;
        if (op==1)
        {
            n++;
            a[n]=x;
            for (auto d:factor[x])
            {
                cnt[d]++;
            }
            sum[n]=cnt[x];
        }
        else 
        {
            int ans=cnt[a[x]]-sum[x];
           // cout<<cnt[a[x]]<<" "<<sum[x]<<" ";
            cout<<ans<<'\n';
        }
    }
    return 0;
}

C

C

这个是我们要给出一个排序,我们会让两个相邻的数变成一个新的数,值为这两个数之和,弄完后长度减一。我们需要知道最后得到的一个数为最大值,并且知道这个排列是怎样的

我发现某一个位置离端点最近的那个距离最小的那个值越大,(越靠中间),那么那个数的利用次数就越大,然后我用了一个结构体求出每一个位置的那个值,排序,最后得到一个排序

那么值怎么求

我看了下那个数据不是很大,就是1e3,所以我就直接模拟了

#include <bits/stdc++.h>
using namespace std;
#define int long long 
int n;
const int mod=1e9+7;
const int maxn=1e3+10;
struct node
{
    int pos,val;
}a[maxn];
int ans[maxn],now[maxn];
bool cmp(node x,node y)
{
    return x.val<y.val;
}
signed  main ()
{
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        a[i].pos=i;
        a[i].val=min(i-1,n-i);
    }
    sort(a+1,a+1+n,cmp);
    for (int i=1;i<=n;i++)
    {
        ans[a[i].pos]=i;
        now[a[i].pos]=i;
    }
    int sum=0;
    int r=n-1;
    while (r>=1)
    {
        for (int i=1;i<=r;i++)
        {
            now[i]=(now[i]+now[i+1])%mod;
        }
        r--;
    }
    cout<<now[1]<<"\n";
    for (int i=1;i<=n;i++)
    {
        cout<<ans[i]<<" ";
    }
    return 0;
}

D

D

题目是给你一个字符串,我们只可以改变一个字符,让udu的子序列变得更加少

我是觉得要改变的那一个字符,一定是其中最关键的字符(可以组成udu最多的字符)

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=2e5+10;
string s;
int pre[maxn],suf[maxn];//i前面的u的数量,i后面的u的数量,udu d可以组成的数量为pre[i]*suf[i],乘法分步
int ud[maxn],du[maxn];//i前面ud的数量,i后面面的du, u作为前面那个有du[i],作为后面的那个有ud[i],分类加法
int n;
signed main ()
{
    cin>>s;
    int u=0,d=0;
    int n=0;
    for (int i=0;i<s.size();i++)
    {
        if (s[i]=='u')
        {
           pre[i]=u;
           ud[i]=ud[i-1];
           u++;
        }
        else if (s[i]=='d')
        {
            d++;
            pre[i]=u;
            ud[i]=ud[i-1]+u;
        }
        else 
        {
            pre[i]=u;
            ud[i]=ud[i-1];
        }
    }
    u=0,d=0;
    for (int i=s.size()-1;i>=0;i--)
    {
        if (s[i]=='u')
        {
            suf[i]=u;
            du[i]=du[i+1];
            u++;
        }
        else if (s[i]=='d')
        {
            d++;
            suf[i]=u;
            du[i]=du[i+1]+u;
        }
        else 
        {
            suf[i]=u;
            du[i]=du[i+1];
        }
    }
    int ans=-1;
    int sum=0;
    for (int i=0;i<s.size();i++)
    {
        if (s[i]=='u')
        {
            int now=du[i]+ud[i];
            if (now>sum)
            {
                ans=i;
                sum=now;
            }
        }
        else if (s[i]=='d')
        {
            int now=pre[i]*suf[i];
            if (now>sum)
            {
                sum=now;
                ans=i;
            }
        }
    }
    if (ans!=-1)
    {
        s[ans]='a';
    }
    cout<<s<<'\n';
    return 0;
}

E

E

题目大意是有1到n个节点,两个节点i,j 差值小于等于k,边权为lcm(i,j),否则就是gcd(i,j),我们要知道这个图的最小生成树

1是一个特殊的点,对于任何数,gcd(1,x)都是1,那么我们把那些和1的距离大于k的点的边权为1

那么对于那些和1的距离小于等于k的点,先和1连,d[i]=i,lcm(1,i),d存的是i点的边权

后面我们再考虑那些和1的距离大于k的距离的点(那些点已经确定和1连了),如果有更少,那就更新边权

这里面还有一个出现了质数的情况

具体操作看代码

#include <bits/stdc++.h
using namespace std;
#define int long long 
const int maxn=2e5+10;
int n,k;
bool isprime(int x)
{
    for (int i=2;i*i<=x;i++)
    {
        if (x%i==0) return false;
    }
    return n>1;
}
int d[maxn];
signed main ()
{
    cin>>n>>k;
    for (int i=2;i<=k+1;i++)//和1的距离小于k,和1的lcm为i
    {
        d[i]=i;
    }
    for (int i=k+2;i<=n;i++)//和1的距离大于k,gcd和1为1
    {
        d[i]=1;
    }
    for (int i=n;i>=k+2;i--)//我们需要利用那些和1连接起来可以让j的点的权值变少
    {
        for (int j=2;i-j>k;j++)//用gcd,需要距离大于k
        {
            d[j]=min(d[j],__gcd(i,j));
        }
        if (isprime(i))//但是如果出现了质数,那么前面的都已经变成了1,
            //两个质数之间的距离最大是100,所以应该不会大于100
            //比如当i为n时
            // j可以为  2 3 4 n-k n-k+1
            //i为n-1时
            //j可以为  2 3 4 n-k
            //我们可以看到越前面的影响越大,如果这一个是质数的话,前面的都会变成1,那么我们就不需要再来取最小权值了,因为1已经是最小的了
        {
            break;
        }
    }
    int ans=0;
    for (int i=2;i<=n;i++)
    {
        ans+=d[i];
    }
    cout<<ans<<'\n';
    return 0;
}

F

F

题目大意是给你n个数,我们可以进行k个操作,让x变成f(x)

f(x)是x二进制中1的个数

我们需要知道k个操作后,最大值最小是多少

对于每一个操作,要想改变最大值,最直接的方法就是把当前的最大值变小,我们可以知道x=f(x)操作一定会变小的

一开始我没有看到k还比较大,超时了

后来看到这个k有点小的,我发现如果最大值是小于等于1的时候,不管怎么变都还是1,所以我进行一个ans,记录进行了i个操作时的答案,如果已经存在,就直接输出,否则就更新,还要注意如果此时已知的最多操作的最小值已经小于等于1的话,不用到k了,直接就输出此时的最小值,不会再变化了,我觉得有点像记忆化的过程

对于此时的最大值,我用到了优先队列

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=2e5+10;
int n,q;
int a[maxn];
int k;
map<int,int>ans;
priority_queue<int>Q;
int r;
int f(int x)
{
    int cnt= 0;
	while (x)
	{
		x = x & (x - 1);
		cnt++;
	}
	return cnt;
}
bool cmp(int x,int y)
{
    return x>y;
}
void solve()
{
    cin>>k;
    if (k<=r)
    {
       cout<<ans[k]<<'\n';
        return ;
    }
    while (r<k&&ans[r]>1)
    {
        int now=Q.top();
        Q.pop();
        now=f(now);
        Q.push(now);
        ans[++r]=Q.top();
        if (ans[r]<=1)
        {
            break;
        }
    }
    cout<<ans[r]<<'\n';
    return ;
}
signed main ()
{
    cin>>n>>q;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
        Q.push(a[i]);
    }
    ans[0]=Q.top(); 
    while (q--)
    {
        solve();
    }
    return 0;
}

G

G

对于n个数,我们需要k对数,我们要求这k对数乘的和最大,每一对数的值为两个数相乘

对于同号,大的和大的,小的和小的,对于假如同号的配对完成,异号也要配对(这个一定要记得),此时一定是正数只剩1个,负号只剩一个,都是绝对值小的(题目说一定可以凑齐k对数)

然后直接模拟

#include <bits/stdc++.h>
using namespace std;
#define int long long 
int n,k;
int sum=0;
priority_queue<int>q1,q2,q;
int a,b;
signed main ()
{
    cin>>n>>k;
    for (int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if (x>=0)
        {
            q1.push(x);
        }
        else 
        {
            q2.push(-1*x);
        }
    }
    while (q1.size()>=2)
    {
        int x=q1.top();
        q1.pop();
        int y=q1.top();
        q1.pop();
        q.push(x*y);
    }
    if (q1.size())
    {
       a=q1.top();
        q1.pop();
    }
    while (q2.size()>=2)
    {
        int x=q2.top();
        q2.pop();
        int y=q2.top();
        q2.pop();
        q.push(x*y);
    }
    if (q2.size())
    {
        b=q2.top();
        q2.pop();
    }
    while (k&&q.size())
    {
        sum+=q.top();
        q.pop();
        k--;
    }
    if (k)
    {
        sum-=a*b;
    }
    cout<<sum<<'\n';
    return 0;
}

H

H

这个不用说了,直接判断求值

#include <bits/stdc++.h>
using namespace std;
#define int long long 
int x,l,r;
signed main ()
{
    cin>>x>>l>>r;
    double ans=0.0;
    if (x>r)
    {
        ans=1;
    }
    else if (l>=x)
    {
        ans=0;
    }
    else 
    {
        int sum=r-l+1;
        int cnt=x-l;
        ans=1.0*cnt/sum;
    }
    printf ("%.16lf",ans);
    return 0;
}

I

I

题目大意是我们有m条边,原本每一条边都有一个权值,但是我们可以牺牲一条边来让一条边的权值变成1

那么这样,如果从1到n这样路只有一条路,而且没有其他没用的路了(所有的路都用到了),那么我们就只有第一条边要用权值,其他的边都是1,(走到第二条路我们牺牲第一条路,反正用不到了),ans=w+cnt-1,w是第一条边的权值,cnt是这一条路除了1之外的点

否则,还存在其他可以不用的路,那么第一条边也可为1,那么ans=cnt

那么我们不用管那些权值,只需记录一下和1连接的权值,可能会用到

每一条边的权值就是1,然后用最短路的方法求出1到n的最路径,然后再这一条路径中,判断有没有可能出现有没有第一种情况

怎么才会出现上面说的第一种情况呢

所有在这一条路径里面的点除了1和n的入度都要等于2,1和n要为1(这个是双向的)

还要其他不在这一条路径里面的点不能出现路径

这种就是第一种情况,其他都是第二种情况

具体看代码

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=2e5+10;
int n,m,in[maxn];
int mi=1e9;
int f[maxn];
int siz[maxn];
int dis[maxn];
int pre[maxn];
vector<int>g[maxn<<2];
bool vis[maxn];
struct node
{
    int pos,dis;
    friend bool operator < (const node x,const node y)
    {
        return x.dis>y.dis;
    }
};
void dijkstra( )
{
    for (int i=1;i<=n;i++)
    {
        dis[i]=1e9;
        vis[i]=false;
    }
    priority_queue<node>q;
    q.push({1,0});
    dis[1]=0;
    vis[1]=true;
    while (!q.empty())
    {
        int now=q.top().pos;
        q.pop();
        for (auto v:g[now])
        {
            if (vis[v]) continue;
            if (dis[v]>dis[now]+1)
            {
                dis[v]=dis[now]+1;
                q.push({v,dis[v]});
                vis[v]=true;
                pre[v]=now;
            }
        }
    }
    return ;
}
signed main ()
{
    cin>>n>>m;
    for (int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back(v);
        g[v].push_back(u);
        in[u]++,in[v]++;
        if (u==1||v==1) 
        {
            mi=w;
        }
    }
    dijkstra();
    int now=n;
    int cnt=0;
    for (int i=1;i<=n;i++)
    {
        vis[i]=false;
    }
    bool yes=false;
    if (in[1]>1) yes=true;
    while (now!=1)
    {
        if (now==n||now==1)
        {
            if (in[now]>1) yes=true;
        }
        else 
        {
            if (in[now]>2) yes=true;
        }
        cnt++;
        vis[now]=true; 
        now=pre[now];
     
    }
    if (yes)
    {
        cout<<cnt<<'\n';
        return 0;
    }
    for (int i=2;i<=n;i++)
    {
        if (!vis[i]&&in[i])
        {
            cout<<cnt<<'\n';
            return 0;
        }
    }
    cout<<mi+cnt-1<<'\n';
    return 0;
}

J

J

这个题,大意是有两个网站,pro,rank,我们有两个操作,

1 查询rank中name的做题量

2 name做x个题,pro更新

rank每t时间都会进行一次更新(每个人),还有在查询某人r时间后更新这个人的题量

我感觉pro可以实时更新每个人的题量,rank要在pro后面,还有规定时间需要更新

然后我们差不多那题目意思来,按照时间的 顺序来

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=2e4+10;
struct node
{    //type  1是更新pro,2是更新rank,3是查询
    int type,t,w,id;
    string name;
    friend bool operator < (const node x,const node y)
    {
        if (x.t==y.t)
        {
            return x.type>y.type;
        }
        return x.t>y.t;
    }
}a[maxn];
int n,t,r;
priority_queue<node>q;//按照时间顺序来更新两个网站的数据
int ans[maxn];
signed main ()
{
    cin>>n>>t>>r;
    map<string,int>cnt1,cnt2;//pro的题量,rank的题量
    for (int i=1;i<=n;i++)
    {
        cin>>a[i].type>>a[i].t>>a[i].name;
        a[i].id=i;
        if (a[i].type==2)
        {
            cin>>a[i].w;
            a[i].type=1;
            q.push(a[i]);
        }
        else //查询
        {
            a[i].type=3;
            q.push(a[i]);
            //查询时,rank也需要更新,t间隔,本来是需要更新所有人的题量,
            //但是此时我们只要询问这一个人,所以我们只更新这个人的,rank每t时间都会更新一下,
            //我们只需要最近的时间更新一下就可以了,那么最近的一次时间是a[i].t/t*t
            //还有在r时间后还需要更新这个人的题数,a[i].t+r
            q.push({2,a[i].t/t*t,a[i].w,a[i].id,a[i].name});
            q.push({2,a[i].t+r,a[i].w,a[i].id,a[i].name});
        }
    }
    while (!q.empty())
    {
        auto x=q.top();
        q.pop();
        if (x.type==1)//我们做的题只在pro上实时更新,然后我们rank是有两种更新时间,询问的是rank上面的题量
        {
            cnt1[x.name]+=x.w;
        }
        else if (x.type==2)
        {
            cnt2[x.name]=cnt1[x.name];
        }
        else 
        {
            ans[x.id]=cnt2[x.name];
        }
    }
    for (int i=1;i<=n;i++)
    {
        if (a[i].type==3)
        {
            cout<<ans[i]<<"\n";
        }
    }
    return 0;
}

K

K

玩游戏,有一个n,有两个人,如果谁不可以操作谁就输了

两种操作

x变成x+1

x变成x/2

x不可以变成0,也不可以变成之前出现过的数

这里我们记录了一个状态

x y z

x是此时的数

y是如果我们左边的那一个区间 1到y(左闭右开),这个算是除二的极限,这个是递减的,单看减少的话,如果出现了y,那么它下次除二的话,一定不可以比y大

z是右边的那个区间 x只加到z ,第一个操作是连续的递增的,那么如果z已经出现过了,那么后面的也一定是不可以的了

然后大体操作就是求每一个状态的状态,如果出现必败,那么就可以得到结果了

#include <bits/stdc++.h>
using namespace std;
#define int long long 
map<int,bool>vis;
int n;
int get(int x,int y,int z)
{
    return 1000000*x+1000*y+z;
}
bool dfs(int x,int y,int z)
    //此时数字为x,后一个操作可以变成的数为比y小的数(这个是除二操作可以得到的最大极限),
    //小于z,这个是加一操作的最大极限
{
    int id=get(x,y,z);
    if (vis.count(id))
    {
        return vis[id];
    }
    if (x+1<z&&!dfs(x+1,y,z))//加一操作,极限都没有变化
    {
        return vis[id]=true;
    }
    if (x/2&&x/2<y&&!dfs(x/2,x/2,y))//除二操作,那么那么除二的极限不要大于x/2,除二是一个变小的
    {
        return vis[id]=true;
    }
    return vis[id]=false;
}
signed main ()
{
    cin>>n;
    for (int i=n;i<2*n;i++)
    {
        if (i/2&&!dfs(i/2,i/2,n))//i/2是必败状态,那么我们要判断i/2这个状态是谁变得,
        {
            if ((i-n)%2==0)//先加到i,再除2,加法进行了偶数次,那么变成i/2是小宁操作后,小红必败
            {
                cout<<"ning\n";
                return 0;
            }
            else //加了奇数次,那么变成i/2是小红操作后,小宁必败
            {
                cout<<"red\n";
                return 0;
            }
        }
    }
    cout<<"draw\n";//如果前面的都没有出现,那么是平手
    return 0;
}

L

L

题目是有一个像一个倒放的楼梯行的网格,我们需要知道从1,1到每一层的最后一个的总数量

但是

还有一些格子是不能走的

我一开始直接来,后来超时了

然后看了题解

注意到m是很小的(m是不能走的位置),m<=10

那么我们不能走的路径里面一定包括这m里面的任意个

怎么选,我们就用到了二进制的方式

遍历1到1<<m(如果这一位是1,那么这条路里面包括这一个障碍)

然后我们会知道对于这些情况,可能会有重复的路径

这里我们就可以考虑到容斥(其实我也有点不太理解)

我们不可以的路径的数量

一个块,就是包括这个块的数量

两个块,要减去这种情况的数量

三个块,就是包括这个块的数量

四个块,要减去这种情况的数量

奇数个障碍,减法,偶数个,加法(对于没有障碍的总路径数)

没有障碍的总路径数有2^n-1 有n-1步,一定可以到达我们想要的位置,有两种方向,所以总数为2^n-1

那么对于那些障碍点,要怎么计算出那些搭配的路径时的数量

我们可以看到

这个箭头方向,转移的是不是和杨辉三角有点相似

所以

对于x,y和xx,yy之间的连线方式有

c(dx+dy-2,dx-1),dx,dy为两点之间的差值

还要注意,这些点的连接一定是按照我们下一步可以走的

xx>=x,yy>=y,我们可以先把x排序一下,然后再判断y是否符合条件,如果不符合条件,那么直接为0,不用管,break

然后就没什么了,具体看代码吧

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int mod=1e9+7;
const int maxn=4e5+10;
int inv[maxn],fac[maxn],invfac[maxn],pow2[maxn];
struct node
{
    int x,y;
    friend bool operator < (const node i,const node j)
    {
        return i.x>j.x;
    }
};
node a[20];
node b[20];
bool cmp(node i,node j)
{
    return i.x<j.x;
}
int n,m;
void init()
{
    inv[1]=1;
    fac[1]=1;
    invfac[1]=1;
    fac[0]=1;
    invfac[0]=1;
    for (int i=2;i<=n*2;i++)
    {
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        fac[i]=(fac[i-1]*i)%mod;
        invfac[i]=(invfac[i-1]*inv[i])%mod;
    }
    pow2[0]=1;
    for (int i=1;i<=n;i++)
    {
        pow2[i]=(pow2[i-1]*2)%mod;
    }
    return ;
}
int c(int n,int m)
{
    int res=fac[n]*invfac[m]%mod*invfac[n-m]%mod;
   // cout<<n<<" "<<m<<" "<<res<<'\n';
    return res;
}
signed main ()
{
   cin>>n>>m;
    init();
    for (int i=1;i<=m;i++)
    {
        cin>>a[i].x>>a[i].y;
    }
    int ans=pow2[n-1];
    for (int i=1;i<(1<<m);i++)
    {
        int cnt=0;
        b[++cnt]={1,1};
        for (int j=0;j<m;j++)
        {
            if (i>>j&1)
            {
                b[++cnt]=a[j+1];
            }
        }
        int flag=-1;
        if (cnt&1)
        {
            flag=1;
        }
        sort(b+1,b+1+cnt,cmp);
        int tmp=pow2[n-1-(b[cnt].x+b[cnt].y-2)];
        for (int i=2;i<=cnt;i++)
        {
            int x=b[i-1].x;
            int y=b[i-1].y;
            int xx=b[i].x;
            int yy=b[i].y;
            if (xx>=x&&yy>=y)
            {
                int tx=xx-x+1;
                int ty=yy-y+1;
                tmp=(tmp*c(tx+ty-2,tx-1))%mod;
            }
            else 
            {
                tmp=0;
                break;
            }
        }
        cout<<flag<<" ";
        ans=(ans+flag*tmp)%mod;
    }
    ans=(ans%mod+mod)%mod;
    cout<<ans<<'\n';
    return 0;
}

标签:cnt,集训营,int,ans,long,牛客,maxn,2023,now
From: https://www.cnblogs.com/righting/p/17091366.html

相关文章