24上半年ACM第八次周赛题解

文章讨论了编程中的几道题目,涉及模拟题目的条件解析、离散数学中的传递性判断、双指针优化算法、二分查找、并查集模板的应用,以及如何避免数据错误和优化解决方案。
摘要由CSDN通过智能技术生成

 

A.题目难度

额,第一题我又出错数据了,当时我写完题解代码没有交,直接跑的数据导致,出现数据错误了,给大家道个歉。

这是一道经典模拟题,一般这类题目题干较长,需要仔细分析条件并写出代码。从这一道题中解析题目条件,题目难度不能为负数;题目难度的平均数要大于等于题目数量,小于等于题目数量二倍;最难的和最简单的差值要大于等于5,小于等于10。分析出这些条件接很好写了。

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,bj=0;
        cin>>n;
        double x=0;//用于计算平均难度,因为可能会有小数情况,所以用double存
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
            x+=a[i];
        }
        for(int i=0;i<n;i++)
        {
            if(a[i]<0){cout<<"小劉你又出错题了"<<endl;bj=2;break;}//遍历一边是否有负数存在
        }
        if(bj==2)continue;//若存在负数,则不用再进行别的操作了
        sort(a,a+n);//排序,再去判断一下最大最小的差
        int p=a[n-1]-a[0];//最大和最小的差
        x=x/n*1.0;
        if(p<5||p>10)bj=1;//判断最大和最小的差的范围
        if(x<n||x>2*n)bj=1;//判断平均难度的范围
        if(bj==1)cout<<"小劉出的题目难度好古怪"<<endl;
        else cout<<"小劉这次出的题目没问题"<<endl;
    }
}

B.离散数学

这其实就是一道定义题,加上暴力优化就行了。首先要先看懂传递性的定义,这其实就不用最多说了,但是有些人上课不听讲,那就再解释一下吧。假设有两个序偶<x,y>,<y,z>,所谓传递性就是一个序偶中的第二个数,即y,如果能够在R中找到以y为第一个数的序偶,那么就需要判断以y为第二个数的序偶的第一个数,即x,以y为第一个数的序偶的第二个数,即z,组成的序偶<x,z>,若所有满足这样条件的序偶组合出的序偶都存在R中,则证明有传递性。在通俗一点的话,拿几个例子来说例如R={<1,1>,<1,3>,<1,2>},这里边<1,1>和<1,2>能组合出<1,2>,并且它在R里,<1,1>和<1,3>能组合出<1,3>,并且它在R里,<1,3>在R中没有以3开头的则不用管它,同理<1,2>也是最后,因为能够组合的全部在R中,不能组合的不用管,所以判断R具有传递性。这道题我跑需随机数的时候没跑超过x的数,现在已经加上去了,需要判断一下。

第一种方法,它的复杂度是n*n,根据数据大概在1e7左右

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
typedef pair<int,int> PII;
vector<PII> a;
bool p[N][N];
int main()
{
    int bj=0;
    int n,x;
    cin>>n>>x;
    for(int i=0;i<n;i++)
    {
        int b,c;
        cin>>b>>c;
        p[b][c]=true;//将存在过的序偶全部标记方便后续处理
        a.push_back({b,c});//以数对的形式放入容器中
        if(a[i].second<=0||a[i].first<=0||a[i].first>x||a[i].second>x)bj=1;
        //判断是否会有序偶超出1到x的范围,超出直接判断没有传递性
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(a[i].second==a[j].first)//遍历到有某一个的序偶的第二个数等于另一个序偶的第一个数
                                       //进入判断条件
            {
                if(p[a[i].first][a[j].second]!=true){bj=1;break;}//存在则不处理,不存在则直接
                                                                 //直接跳出,直接判断没有传递                            
                                                                 //性
            }
        }
        if(bj==1)break;
    }
    if(bj==1)cout<<"No";
    else cout<<"Yes";
}

第二种方法,时间复杂度大概是x*x*x,根据数据大概1e5左右

#include<bits/stdc++.h>
using namespace std;
const int N=110;
typedef pair<int,int> PII;
vector<PII> c;
bool bj[N][N];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,x;
        cin>>n>>x;
        for(int i=0;i<n;i++)
        {
            int a,b;
            cin>>a>>b;
            c.push_back({a,b});
            bj[a][b]=true;//将存在过的序偶标记,方便后续处理
        }
        for(int i=0;i<n;i++)if(c[i].first>x||c[i].first<0||c[i].second<0||c[i].second>x)    
        {cout<<"No\n";continue;}//如出现不在1到x范围的数字则直接不存在传递性
        int p=0;//一个标记
        for(int i=1;i<=x;i++)
        {
            for(int j=1;j<=x;j++)
            {
                if(bj[i][j]==true)//遍历所有序偶的可能性,若存在则直接进入
                {
                    for(int k=1;k<=x;k++)   //当某一个序偶存在时,去遍历以该序偶的第二个数字开                                     
                    {                       //头的序偶,若不存则不做处理,若存在则需要判断组合
                        if(bj[j][k]==true)  //出来的新的序偶是否存在,若不存在则直接跳出输出没
                        {                   //有传递性,否则就是有传递性
                            if(bj[i][k]!=true){p=1;break;}
                        }
                    }
                }
                if(p==1)break;
            }
            if(p==1)break;
        }
        if(p==1)cout<<"No\n";
        else cout<<"Yes\n";
    }
}

 

C.数学建模

这是一道双指针优化的题目,正常先直接双重for循环,再优化。根据题目要求,先找出所有以1开头,以x结尾的数字串,然后再找出它们中最长的输出即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int main()
{
    int n,x;
    cin>>n>>x;
    for(int i=0;i<n;i++)cin>>a[i];
    int t=-1,bj=0;//t用来标记,如果不存在答案数字串的话,则t的数值也不会更新,直接输出就行
    for(int i=0,j;i<n;i++)
    {
        if(a[i]==1)//当开头是1的时候进入
        {
            j=i+1;//从1的下一位开始遍历
            bj=0;
            while(a[j]<=x&&a[j]>1)
            {
                if(a[j]==x){bj=1;break;}//遇见x跳出的话,表示是符合条件的数字串,更新t的值
                                        //不是遇见x的话,便是该数字串不符合条件,不更新t的值
                j++;
            }
        }
        if(bj==1)t=max(t,j-i+1);
    }
    cout<<t;//最后输出即可
}

D.序列

这是一道二分的题目,实际上如果有人学过bound的话,这道题可以直接秒掉的。通过二分去寻找目标数字的左边界,当二分结束的时候,需要判断是否存在,因为二分肯定会结束,但不一定存在答案,所以需要在二分结束时去判断是否存在。

这一道题不能使用endl换行,因为endl需要清空缓冲区,所以会增加时间导致超时,最好直接使用“\n”换行

第一种方法,二分解法

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main()
{
	int n,q;
	cin>>n>>q;
	for(int i=0;i<n;i++)cin>>a[i];
	while(q--)
	{
		int x;
		cin>>x;
		int l=0,r=n-1;//确定左右边界,开始二分
		while(l<r)
		{
			int mid=l+r>>1;//每次取中点判断
			if(a[mid]>=x)r=mid;//若符合条件,则更新右边界
			else l=mid+1;//否则,更新左边界
		}
		if(a[l]!=x)cout<<"-1"<<"\n";//最后判断是否存在,不存在输出-1
		else cout<<l+1<<"\n";//否则,输出边界加1,因为我的数组下标是从1开始的
	}
	return 0;
}

第二种解法,map解法

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
map<int,int>mp;
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(mp[a[i]]==0)//将每个数第一次出现的位置存起来
        mp[a[i]]=i;
    }
    for(int i=1;i<=m;i++)
    {
        int t;
        cin>>t;
        if(mp[t]==0)//判断是否出现过,出现过的话,值应该为下标,未出现过的话,值为0
        cout<<-1<<"\n";
        else cout<<mp[t]<<"\n";
    }
    
    return 0;
}

第三种解法,bound,STL大法

#include<bits/stdc++.h>

using namespace std;
const int N=1e6+10;
int q[N];

int main(){
    int n,m;cin>>n>>m;
    for(int i=0;i<n;i++)cin>>q[i];
    while(m--){
        int x;cin>>x;
        int t=lower_bound(q,q+n,x)-q;//lower_bound能够直接返回某一个数字在序列中出现过的第一个        
                                     //位置的地址,所以需要减去数组的首地址,数组首地址即为数组             
                                     //名,固直接减数组名即可确定某一个数字第一次出现的位置
        if(q[t]==x)cout<<t+1<<'\n';
        else cout<<-1<<'\n';
    }
    return 0;
}

E.朋友

经典并查集的模板,就增加了一个询问的部分,所以在版子增加一些即可

#include<bits/stdc++.h>
using namespace std;
const int N=5100;
int q[N];
int find(int x){
    if(q[x]!=x)q[x]=find(q[x]);
    return q[x];
}//并查集经典函数,不做解释
 
int main()
{
    int n,m,x;
    cin>>n>>m>>x;
    for(int i=1;i<=x;i++)q[i]=i;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        cin>>x>>y;
        if(find(x)!=find(y))q[find(x)]=find(y);
    }
    while(m--)//询问的部分
    {
        int p;
        cin>>p;
        if(find(p)==find(1))cout<<"Yes\n";
        else cout<<"No\n";
    }
    return 0;
}
0

F.馈赠

签到题

#include<bits/stdc++.h>
using namespace std;
int main()
{
    string a;
    cin>>a;
    cout<<a.size();
    return 0;
}

G.巧克力

经典BFS,和之前的农场的水坑几乎差不多,只需要在加一个求面积的即可

#include<bits/stdc++.h>
using namespace std;
const int N=120;
typedef pair<int,int> PII;
int a[N][N];
bool b[N][N];
int x1=0,s1=0,s2=0;
int dx[4]={-1,1,0,0},dy[4]={0,0,1,-1};// 四个方向
void bfs(int x,int y)
{
    x1++;//求个数
    queue<PII> q;
    q.push({x,y});
    while(q.size())
    {
        s1++;//求面积
        PII p=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int xx=p.first+dx[i],yy=p.second+dy[i];
            if(b[xx][yy]==true)
            {
                b[xx][yy]=false;
                q.push({xx,yy});
            }
        }
    }
    s1--;//最后会多走一次,减去
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            cin>>a[i][j];
            if(a[i][j]==0)b[i][j]=true;
        }
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            if(b[i][j]==true){s1=0;bfs(i,j);s2=max(s2,s1);//更新最大值}
        }
    }
    cout<<x1<<" "<<s2;
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值