第二届“梦羽杯”题解

前言

特别鸣谢出 (蒯) 题人:CJY

A 工程制图的往事

原题:NOIP2008普及组T4 《立体图》

B 颠倒的规则

原题:HAOI2007 反素数

C 知识的边界

题意:

输入一个大于4的偶数n,将其分解为两个素数之和输出。如果有多种分解方案,请输出字典序最小的那个一个。\(4\leq n\leq1000\)

题解

先预处理:枚举每个小于n的数是否为素数
然后枚举合法情况,找到的第一个合法情况即输出

代码

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n, f[_];
int main()
{
    n=read();
    for(re int i=2;i<=n;++i)
    {
        for(re int j=2;j*j<=i;++j) if(i%j==0) {f[i]=1; break;} //枚举素数
    }
    for(re int i=2;i<=n;++i)
    {
        if(f[i]==0 && f[n-i]==0) 
        {
            cout<<n<<'='<<i<<'+'<<n-i<<endl;
            return 0;
        }
    }
    return 0;
}

D 面壁者的选拔

题意:

n个人排成一列,从左至右123报数,每次报道3的人淘汰,然后从右至左重新123报数
重复上述过程直至只剩两个人,然后报1的淘汰,询问最后留下的人编号
\(n<=2000\)

题解:

观察到数据范围较小,可以使用\(O(n^2)\)的算法
然后按照题意模拟即可,具体实现看代码中的注释

代码:

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n, f[_];
int main()
{
    n=read();
    int num=0, tot=n, now=1, fff=0; 
	//tot是目前所剩人数,fff用于判断下一轮报数方向
    while(tot>2)//这层循环负责代表每一轮(正反各一次)
    {
        int cnt=0;
        for(re int i=1;i<=n && tot>2;++i)
        {
            if(f[i]) continue; //f[i]用于记录是否筛除
            cnt++; if(cnt>3) cnt=1;
            if(cnt==3) f[i]=1, tot--;
        }
        if(tot==2){ fff=1; break;}
        cnt=0;
        for(re int i=n;i>=1 && tot>2;--i)
        {
            if(f[i]) continue;
            cnt++; if(cnt>3) cnt=1;
            if(cnt==3) f[i]=1, tot--;
        }
        if(tot==2){ fff=0; break;}
    }
    if(fff)
    {
        for(re int i=n;i>=1;--i) if(!f[i]){ cout<<i<<endl;return 0;}
        return 0;
    }
    else 
    {
        for(re int i=1;i<=n;++i) if(!f[i]){ cout<<i<<endl;return 0;}
        return 0;
    }
    return 0;
}

E 前进四!!!!

题目描述:

在数轴上给定n个点,要求在删除不超过m个点的情况下,最大化两点间最小距离
0<=m<=n<=50000

题解:

显然删除点的数量与最小距离单调相关,所以二分最小距离,然后每次check删除即可
复杂度是\(O(n*log_2n)\)

代码

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n, d[_], m;
in int check(int lim)
{
    int lst=0, used=0;
    for(re int i=2;i<=n;++i)
    {
        if(d[i]-lst<lim){ used++; continue;}
        lst=d[i];
    }
    return used<=m;
}
int main()
{
    int len=read();
    n=read(), m=read(); d[1]=0, d[n+2]=len;
    for(re int i=2;i<=n+1;++i) d[i]=read();
    n+=2;
    int l=0, r=len,ans=0;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid)) l=mid+1, ans=mid;
        else r=mid-1;
    }
    cout<<ans<<endl;
    return 0;
}

F 地球的气运

题意:

一年十二个月已知每个月的开销,每个月月初可以得到300元。
给定如下规则:
1.银行每次存款的金额必须是整百
2.会在月初领钱后,把除去开销的余额按能存就存的规则存入银行
3.存入银行后不得去除
要求判断会在第几个月出现余额不够开销 or 计算最后总资产

题解:

模拟即可,具体看代码

代码:

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
#define int ll
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n;
signed main()
{
    n=12;int now=0, ku=0;
    for(re int i=1;i<=n;++i)
    {
        now+=300;
        int x=read();
        if(now<x){ cout<<-i<<endl;return 0;}
        now-=x;
        ku+=now-now%100, now-=(now-now%100);
    }
    cout<<now+(ku*1.2)<<endl;
    return 0;
}

G 啊!!!!水滴!

题意:

有一张\(2*n\)的扫雷地图,只有第一列有雷,已知第二列的所有数字信息,求第一列有多少种构造方式
\(n\leq2000\)

题解:

这次直接分享一种可以做 \(n\leq10^6\) 的做法,考虑动态规划解计数题

  1. 设状态\(f_{i,j,k}\)(其中$ j,k \in {1,0}$,代表有无雷)表示:
    第一列中目前填到了i,第i个位置,填的是j,第i+1个位置填的是k的方案总数
    (有点拗口,多理解几遍叭)
对动态规划有疑惑的同学可以参考一下之前发的第一届梦羽杯题解对动态规划的理解
  1. 然后考虑状态转移:
    根据第i位的数字,枚举状态转移即可,详情见代码,如有任何问题可以私聊我

代码:

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n,f[_][2][2], a[_];
int main()
{
    n=read();
    for(re int i=1;i<=n;++i) a[i]=read();
    if(a[1]>2 || a[n]>2) {cout<<0<<endl; return 0;}
    if(n==1)
    {
        if(a[1]<=1) puts("1");
        else puts("0");
        return 0;
    }
    if(a[1]==0) f[1][0][0]=1;
    if(a[1]==1) f[1][1][0]=f[1][0][1]=1;
    if(a[1]==2) f[1][1][1]=1;
    for(re int i=2;i<=n-1;++i)
    {
        if(a[i]>3) { cout<<0<<endl; return 0;}
        if(a[i]==0) f[i][0][0]=f[i-1][0][0];
        if(a[i]==1)
        {
            f[i][0][0]=f[i-1][1][0];
            f[i][1][0]=f[i-1][0][1];
            f[i][0][1]=f[i-1][0][0];
        }
        if(a[i]==2)
        {
            f[i][1][0]=f[i-1][1][1];
            f[i][0][1]=f[i-1][1][0];
            f[i][1][1]=f[i-1][0][1];
        }
        if(a[i]==3) f[i][1][1]=f[i-1][1][1];
    }
    if(a[n]==0) cout<<f[n-1][0][0]<<endl;
    else if(a[n]==1) cout<<f[n-1][1][0]+f[n-1][0][1]<<endl;
    else if(a[n]==2) cout<<f[n-1][1][1]<<endl;
    else cout<<0<<endl;
    return 0;
}

I 澳洲移民

题目描述

地球即将被三体人占领,你将帮助设计一种方案来把人们全部运送到澳大利亚。
所有三体提供的船只都是一样的。这种船虽然能在太平洋和印度洋上航行,但是却最多乘两人,而且载重有一个限度。现在三体人要求我们要节约费用,所以要尽可能地使用的船。你的任务是读入船的载重量w,参加移民的人数n以及每个人的体重ti,计算出所需要的船的数目。
\(1\leq t_i\leq w\leq300, 1\leq n\leq 30000\)

题解

贪心算法,即一个轻的人最好配上一个重的人同船。
具体做法:先从小到大排序,然后最轻的配最重的,若超载,则配第二重的,以此类推,最后未配对的胖子必须单人单船

代码

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n,w, f[_], t[_], ans;
int main()
{
    w=read(),n=read();
    for(re int i=1;i<=n;++i) t[i]=read();
    sort(t+1,t+n+1);
    int now=n;
    for(re int i=1;i<=n && now>=i;++i)
    {
        while(t[i]+t[now]>w && now>i){f[now]=1; now--;}
        if(now==i){f[now]=1; break;}
        ans++,now--;
    }
    for(re int i=1;i<=n;++i) ans+=f[i];
    cout<<ans<<endl;
    return 0;
}

J 银河纪元409年,我们的《星空》

题目描述

给出n个区间,你要找到其中的一些区间使得这些区间的长度和最大,要求这些区间不能有任何重合的部分。
\(n\leq 2000, l_i,r_i \leq 2000\)

题解

考虑动态规划(DP),设\(f_i\)表示目前选到第i个位置(前面的位置都不能选了),最大的已选区间长度

代码

/*
date:23/10/15
writer:YZHX
*/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0, x=1; char ch=get;
    while((ch<'0' || ch>'9') && ch!='-') ch=get;
    if(ch=='-') x=-1, ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t*x;
}
const int _=1e5+23;
int n, f[_];
struct edge{
    int l,r,len;
}e[_];
in int cmp(edge a,edge b)
{
    return (a.l==b.l ? a.len>b.len : a.l<b.l);
}
int main()
{
    n=read();
    for(re int i=1;i<=n;++i) e[i].l=read(), e[i].r=read(), e[i].len=e[i].r-e[i].l+1;
    sort(e+1,e+n+1,cmp); //按区间左端点排序
    int ans=0;
    for(re int i=1;i<=n;i++)
    {
        f[i]=e[i].r-e[i].l+1; int mx=0;
        for(re int j=1;j<=i-1;++j)
            if(e[j].r<e[i].l) mx=max(f[j],mx);
        f[i]+=mx;ans=max(f[i],ans);
    }
    cout<<ans<<endl;
    return 0;
}
posted @ 2023-10-16 10:02  yzhx  阅读(55)  评论(0编辑  收藏  举报