2021牛客小白月赛39

2021牛客小白月赛39(A+B+C+E+G+H)

A.憧憬

题目链接:https://ac.nowcoder.com/acm/contest/11216/A

题目描述:
给定n个向量的起点和终点x1,y1,x2,y2,再给出一个目标向量,求能否由n个向量中的两个相加构造出一个与目标向量平行的向量(注意,平行包括方向相同或相反)
保证x1!=x2,y1!=y2,保证两个向量相加不为零向量。

输入描述:
第一行给定一个整数n
接下来n行每行四个整数x1,y1,x2,y2,表示第i个向量的起点和终点
最后一行给出四个整数x1,y1,x2,y2,表示目标向量的起点和终点
1<=n<=1000,1<=x1,y1,x2,y2<=10000

输出描述:
若可以,输出“YES”
否则输出“NO”(均不含引号)

示例1
输入
5
1 2 4 8
2 4 5 10
1 2 2 4
3 5 4 6
6 10 7 11
1 1 4 7
输出
YES
说明
1号向量与3号向量相加

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
struct node{
	int x,y;
}a[N];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		a[i].x=x2-x1;
		a[i].y=y2-y1;
	}
	int x1,y1,x2,y2,x3,y3;
	cin>>x1>>y1>>x2>>y2;
	x3=x2-x1,y3=y2-y1;
    int m1=__gcd(x3,y3);
    x3/=m1;
    y3/=m1;
	int flag=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			int x0=a[i].x+a[j].x;
			int y0=a[i].y+a[j].y;
			int m0=__gcd(x0,y0);
            x0=x0/m0;
            y0=y0/m0;
            if(x0==x3&&y0==y3)
            {
                cout<<"YES";
                return 0;
            }
            
		}
	}
	cout<<"NO";
	return 0;
}

签到题之一,高中数学向量知识。。。求出两个向量a,b(用终点向量减起点向量),c=a+b求出和向量,假如c(x,y),目标向量为(x1,y1),那么直接判断x乘y1是否等于y乘x1即可,如果是就为"YES",否则就为"NO".我做的时候想的稍微复杂点,用了最大公约数,其实大可不必,直接这样判断更简单。

B.欢欣

题目链接:https://ac.nowcoder.com/acm/contest/11216/B

题目描述:
兴趣使然,他加入了Oier的行列,很快,他就学会了发QAQ……
小P非常喜欢发QAQ,但是他经常不慎打错,请你帮帮他在他的话里找到第一个正确的QAQ吧!
给定一个字符串,输出第一个QAQ的位置(保证有解)

输入描述:
一行一个字符串
3<=len<=10^6,len表示字符串长度

输出描述:
输出一个数,表示第一个QAQ中第一个Q的位置

示例1
输入
qAQaqQAqQAQ
输出
9

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
string s;
int main()
{
    cin>>s;
    int len=s.length();
    int flag=0,pos;
    for(int i=0;i<len;i++)
    {
        if(s[i]=='Q'&&s[i+1]=='A'&&s[i+2]=='Q')
        {
            cout<<i+1;
            return 0;
        } 
        
    }
    return 0;
}

最简单的签到题,直接判断即可,这里就不多说了。

C.奋发

题目链接:https://ac.nowcoder.com/acm/contest/11216/C
在这里插入图片描述
在这里插入图片描述
code:

#include<bits/stdc++.h>
using namespace std;
const int N=3e6+10;
long long ans=0;
int a[N],b[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[i],&b[i]);
    for(int i=1;i<=n;i++)
    {
        int l=min(a[i],b[i]);
        int r=max(a[i-1],b[i-1]);
        if(l>=r)
        {
            ans+=l-r;
            if(a[i-1]!=b[i-1]) ans++;
        }
    }
    printf("%lld",ans);
    return 0;
}

这道题我理解错意思了…呜呜呜~看完题解才知道这道题原来不是那么难。这题是严重被榜带歪的题。。。比赛时只有34人写出来:)。我当时也被吓到了,再加上理解错题意,就寄了。按照题意,a,b两个序列为递增序列,所以直接暴力模拟即可。

D.绝望

题目链接:https://ac.nowcoder.com/acm/contest/11216/D
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
明天更。。。

E.迷惘

题目链接:https://ac.nowcoder.com/acm/contest/11216/E
在这里插入图片描述
在这里插入图片描述
code:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
vector<int> a;
long long ans=0;
void init(int n)
{
    while(n)
    {
        if(n&1) a.pb(1);
        else a.pb(0);
        n=n/2;
    }
    
}
int ksm(int x,int y)
{
     int res=1;
     while(y)
     {
         if(y&1) res=res*x;
         x=x*x;
         y/=2;
     }
     return res;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int num;
        cin>>num;
        if(num==0)
        {
            continue;
        }
        a.clear();
        init(num);
        reverse(a.begin(),a.end());
        for(int i=a.size()-1;i>=0;i--)
        {
            if(a[i]==0)
            a.pop_back();
            else{
            break;
            }
        }
        reverse(a.begin(),a.end());
        int len1=a.size()-1;
        for(int i=0;i<a.size();i++)
        {
            ans+=a[i]*ksm(2,len1);
            len1--;                
        }   
    }
    cout<<ans<<"\n";
    return 0;
}

签到题之一,但这道题我又理解错意思了,wa了三发,啊啊啊啊,好烦!我之前的理解是分别求出来,正确的理解是分别求出来的数的和。。。而中间又因为一些细节上没做处理,所以wa了三发。这道题不是太难,我用了vector,但可以不用的,做法很多,不过我想锻炼自己用数据结构的能力,所以用了数据结构的知识。

F.孤独

题目链接:https://ac.nowcoder.com/acm/contest/11216/F
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以后更。。。

G.冷静

题目链接:https://ac.nowcoder.com/acm/contest/11216/G
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
code:

#include<bits/stdc++.h> 
using namespace std;
const int N=1e7+5;
int vis[N],p[N],f[N],ans[N],c[N];
struct Query{
	int n,k,id;
}query[N];
void init()
{   
	int tot=0;
	memset(vis,0,sizeof(vis));
	memset(p,0,sizeof(p));
	vis[0]=vis[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(vis[i]==0)
		{
			p[tot++]=i;
			f[i]=i;
		}
		for(int i1=0;i1<tot&&N>=p[i1]*i;i1++)
		{
			vis[i*p[i1]]=1;
			f[i*p[i1]]=p[i1];
			if(i%p[i1]==0)				
			break;
			
		}
	}
	
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
bool compare1(Query x,Query y)
{
	return x.n<y.n;
}
void ins(int x) {for(;x<=10000000;x+=x&-x) ++c[x];}
int ask(int x) {int r=0;for(;x;x-=x&-x) r+=c[x];return r;}
int main()
{
	init();
	int q;
	q=read();
	for(int i=1;i<=q;i++)
	{
		query[i].n=read();
		query[i].k=read();
		query[i].id=i;
	}
	sort(query+1,query+1+q,compare1);
	
	for(int i=1,j=1;i<=q;i++)
	{		
		while(j<query[i].n) ins(f[++j]);
		ans[query[i].id]=query[i].n-1-ask(query[i].k-1);
	}
	
	for(int i=1;i<=q;i++)
	cout<<ans[i]<<"\n";
	return 0;
}

这道题用了欧拉筛,离线查询和树状数组,也可以不用树状数组,可以用线段树,平衡树,但不管是哪个树我都不会。。。我好弱呜呜呜。看了题解和大佬的视频的讲解勉强弄懂了,写出了AC代码来。就是树状数组那里为什么要那样写我没搞明白,但我理解它那样做的用途,这就很尴尬了。
题目的意思是要让你求1~n中有多少个数可以由不小于k的质数的乘积来表示,注意质数可以为1个,也可以为多个。我们的做法是:用欧拉筛算出i的最小质因子f[i],然后用struct 结构体将q次查询的n,k,i储存起来,i为查询顺序数。然后根据n的大小来用sort排序,排序是从小到大。然后遍历,算出当前n的答案,并将答案储存在ans数组里,之后离线查询答案即可。在算当前n的答案时就用了树状数组来求。
1.欧拉筛筛出i的最小质因子:

void init()
{   
	int tot=0;
	memset(vis,0,sizeof(vis));
	memset(p,0,sizeof(p));
	vis[0]=vis[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(vis[i]==0)
		{
			p[tot++]=i;
			f[i]=i;
		}
		for(int i1=0;i1<tot&&N>=p[i1]*i;i1++)
		{
			vis[i*p[i1]]=1;
			f[i*p[i1]]=p[i1];
			if(i%p[i1]==0)				
			break;
			
		}
	}
	
}

有一点要注意的是,f[i]是会更新的!比如在欧拉筛中i=4时,算出的f[4✖3]=3,也就是f[12]=3,显然是错的,但不要因为这个就否定上述这个做法,因为f[6✖2]=2!而且这个是一定会更新到正确答案的。
2.存储n,k,i,并sort排序:

struct Query{
	int n,k,id;
}query[N];
bool compare1(Query x,Query y)
{
	return x.n<y.n;
}
for(int i=1;i<=q;i++)
	{
		query[i].n=read();
		query[i].k=read();
		query[i].id=i;
	}
	sort(query+1,query+1+q,compare1);

3.遍历算出当前n的答案:

void ins(int x) {for(;x<=10000000;x+=x&-x) ++c[x];}
int ask(int x) {int r=0;for(;x;x-=x&-x) r+=c[x];return r;}
for(int i=1,j=1;i<=q;i++)
	{		
		while(j<query[i].n) ins(f[++j]);
		ans[query[i].id]=query[i].n-1-ask(query[i].k-1);
	}

这里着重说下怎么求这个答案。先举个例子,假设当前n=5,k=3,2,3,4,5分别对应的最小质因子为2,3,2,5,那么:
在这里插入图片描述
也就是说最小质因子为2的数有两个,3的有一个,5的有一个,因为k=3,答案就从3和5里选,将这两个的总数相加即可,也就是1+1=2,答案就是2.如果之后的n为7,k为2,那么:
在这里插入图片描述
也就是2+1+1+1=5.以此类推就可求出答案。而就是这时候用树状数组来维护。
ins和ask函数就是树状数组的精髓部分了,ins函数是求出上面图片最小质因子对应有几个数,而ask函数则是将ins函数传进去的最小质因子对应的个数相加到ask的参数对应的最小质因子的个数为止,比如n为7,k为4的话,ask传进去3,ask函数的返回值就为2+1=3,答案就是n-1-3=3。
等明天弄懂了树状数组就解释这个做法的原理。
——————————————————————
懂了,树状数组的核心在于lowbit函数,也就是类似x&-x,返回值是二进制表示中最右的1,比如,10010,返回的二进制数就是10,对应的十进制数为2,在更新函数ins中我们就一直用lowbit函数来更新:

void ins(int x) {for(;x<=10000000;x+=x&-x) ++c[x];}

最大不超过数组的最大长度,从题目中我们就可得知应不超过3e6,适当大点也没关系。为什么要更新到数组最大长度呢?我觉得是为了以后的查询,在查询中会一直减lowbit函数,减到不能减为止,加起来的数就是我们要查询的值了,而如果在ins函数中不更新到最大长度,是得不了正确答案的。具体可见下图:
在这里插入图片描述

在这里插入图片描述
这两张图来自:https://blog.csdn.net/bestsort/article/details/80796531
大家可以参考上面的博客来学习树状数组。
第一张为十进制版本,第二张为二进制版本。绿色的为更新过程,黑色的为查询过程,所以这就很直观的能看出为什么要一直更新直到更新到数组最大长度了还有他的更新和查询的原理了。

H.终别

题目链接:https://ac.nowcoder.com/acm/contest/11216/H
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
#define ll long long
ll a[N],b[N],pre[N],last[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=a[i];
    } 
    for(int i=1;i<=n;i++)
    {
        int d=max(0ll,b[i]);
        b[i]-=d,b[i+1]-=d,b[i+2]-=d;
        pre[i]=pre[i-1]+d;
    }
    for(int i=1;i<=n;i++)
        b[i]=a[i];
    for(int i=n;i>=2;i--)
    {
        int d=max(0ll,b[i]);
        b[i]-=d,b[i-1]-=d,b[i-2]-=d;
        last[i]=last[i+1]+d;
    }
    if(n<=2)
    {
        cout<<0;
        return 0;
    }
    ll ans=1e18;
    for(int i=1;i<=n;i++)
    {
        ans=min(ans,pre[i-1]+last[i+2]);
    }
    cout<<ans;
    return 0;
}

这道题比较难,我们可以先假设没用魔法,那么就用贪心的策略,每次斩三只,这就能使斩击次数最少,用for循环遍历,每次都要砍到当前十七兽血量为0,如果当前的血量为负数或0就不用砍了,这样就是O(n)的复杂度,加上魔法的话,最简单的想法就是枚举用魔法的位置,每次枚举时都用上述的方法来砍,那么n*O(n)也就是O(n^2)的复杂度了。由于常数过大,n最大可为10的6次方,所以这样做必然超时。于是我们要想方法减小时间复杂度。
在这里插入图片描述
图片里的i和i+1代表的是魔法使用的位置,所以我们要求的就是前i-1位置总共的斩击次数加上i+2~n位置斩击次数。所以我们就会想到用前缀和数组pre,和后缀和数组last,分别储存斩击次数,那么当前的斩击次数就位pre[i-1]+last[i+2],算的时候是要选这些当中的最小值,故用上述图片里的式子,ans要设置为1e18,以防这两个数组相加大于一开始的ans,就是要保证一开始ans等于这两个数组相加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值