2023 年 华东师范大学“图森未来杯”校赛 题解

目录

A. 命题人的疏忽(构造 / 交互)

翻译:

思路:

代码:

B. 社恐的舞会 I(构造)

思路:

代码:

D. 开灯(签到)

思路:

代码:

E. 社恐的舞会 II(构造 / 数学)

思路:

代码:

G. 切蛋糕(签到 / 构造)

思路:

代码:

 H. 套娃(CDQ分治)

思路:

代码:

I. 宝箱(线性dp)

思路:

代码:


A. 命题人的疏忽(构造 / 交互)

这是一道交互题。

Cuber QQ 正在做 CuberOJ 上的一道难题。这是一道有着 n 组评测数据的传统算法竞赛题,每组测试数据的第一行都是一个整数,表示这组测试数据的编号:第一组数据的第一行是一个 1,第二组数据的第一行是一个 2,以此类推。每组测试数据的答案都是 0 或 1。

CuberOJ 的评测机制与 ICPC 竞赛类似。它会按照编号逐一使用评测数据来测试提交的程序。如果选手的程序通过了一组评测数据,那么评测机会继续评测下一组数据;而如果选手的程序输出了错误的答案,那么评测机会反馈WA并且不再进行后续的评测。如果在使用某组数据进行评测时,程序发生了运行错误(比如除零、数组越界导致的段错误),那么评测机会反馈RE并同样不再进行后续评测。如果程序通过了所有的测试数据,那么评测机会反馈AC

CuberOJ 与 EOJ 不同,它并不会告诉 Cuber QQ 具体错在第几个测试点,而只会反馈一个最终结果——ACWA或者RE。Cuber QQ 觉得,如果他能够知道他的程序在第几个测试点WA了,那么他就能够很轻松地试出所有测试数据的答案。

在 Quber CC 的指点下,Cuber QQ 发现,如果他在某些测试数据上故意构造一句导致RE的语句,就可以间接地推断出一些信息。所以即便 CuberOJ 不告诉他具体发生错误的测试点编号,他也有方法试出所有正确的答案。而现在,帮助 Cuber QQ 试出正确答案的重任落到了你的头上。你可以进行多次尝试,每次尝试你可以用一行 n 个整数来表示你的程序的构造方式:0 表示让你的程序输出 0,1 表示让你的程序输出 1,而 −1 则表示你故意构造一个RE。比如,如果你想构造以下这个程序,请输出0 1 -1

input(n);
if (n == 1) print(0);
if (n == 2) print(1);
if (n == 3) n = n / 0;

在你的每次尝试后,交互器会告诉你这个程序的运行结果,AC或者WA或者RE。如果你得到了一个AC,请立即结束你的程序,并且你会通过本题的一个测试点。

请注意,你最多有 n+1 次尝试机会。如果你没能在这 n+1 次尝试中输出完全正确的答案,那么你无法通过此题。

输入格式

在交互开始前,你需要先读入一个整数 n (1≤n≤100),表示测试数据的组数。

然后你可以开始交互。请你在一行中输出 n 个整数,每个整数都是 0,1,−1 之一,分别表示你想对对应的一组测试数据构造答案 0、构造答案 1、或构造一句会导致RE的语句。

对于你的每一次尝试,交互器都会在单独的一行中输出运行结果:AC或者WA或者RE,表示你构造的程序的运行结果。因此,当你的程序输出结束后,你都应该读入交互器返回的信息。如果你读到了AC,请立即终止你的程序,否则可能会被判定为超时。

输出格式

注意,你需要在每输出一行之后执行 flush 操作,否则你提交的程序有可能会被判定为Idleness limit exceeded

适用于各种语言的flush函数如下:

  • 对于 C++ 语言,你可以使用fflush(stdout);
  • 对于 Java 语言,你可以使用System.out.flush();
  • 对于 Pascal 语言,你可以使用flush(output);
  • 对于 Python 语言,你可以使用sys.stdout.flush()

样例

input

5
RE
WA
AC

output

1 0 1 1 -1
1 0 1 1 1
1 0 1 1 0

提示

样例解释:

假设 5 组测试数据的正确答案分别是1 0 1 1 0

首先,交互器向选手程序输入5,即数据的组数。选手程序猜测前 4 组数据是1 0 1 1,并在第 5 组构造一个RE,因此输出1 0 1 1 -1。前 4 组答案正确,第 5 组RE,因此交互器向选手程序输入结果RE

接着,选手程序猜测前5组数据是1 0 1 1 1,输出1 0 1 1 1,由于前4组答案正确、第5组答案错误,因此交互器向选手程序输入WA

最后,选手程序输出1 0 1 1 0。答案全部正确,交互器向选手程序输入AC。此时选手程序需要自行终止(否则会超时)。

翻译:

有一串0和1组成的数列,你要猜出来它:

从第一位开始,把你的数列和答案数列作比较,如果不一致反馈一个WA,如果你给的是-1,返回一个RE,如果去全部一样,返回AC

要求在不超过n+1次猜测中把得到AC

思路:

交互题,构造数列全部为-1,然后一位一位地改为0

如果返回WA,说明这位应该是1,如果返回RE,说明就是0

出现AC退出即可

代码:

#include
using namespace std;
typedef long long ll;
int main(){
    int n;
    cin>>n;
    int flag=0;
    vectora(n+3,-1);   //构造全是-1
    int cnt=1;
    while(1){
        string s;
        a[cnt++]=0;     //一位一位赋值为0

        for(int i=1;i<=n;i++)cout<>s;
        if(s=="AC"){
            return 0;
        }
        if(s=="RE"){    //就是0
            continue;
        }
        if(s=="WA"){    //应该是1
            a[cnt-1]=1;
        }
    }
    return 0;
}

B. 社恐的舞会 I(构造)

Cuber QQ 是一位社交恐惧者。为了显得他并没有那么害怕社交,他打算请他的 n 位社恐朋友们开一场舞会。

Cuber QQ 准备了一个超大的舞池。这个舞池可以看作一个平面直角坐标系,因为 Cuber QQ 喜欢整齐划一的感觉,所以他想把每个朋友安排在一个格点上。当然,他可以在一个格点上安排多位朋友。

Cuber QQ 觉得,这些社恐朋友们肯定不希望这场舞会过于拥挤。以 Cuber QQ 的经验,社恐们只会和与自己直线距离小于或等于 1 的人们社交。而且,一旦和超过 2 人社交,他们就会非常尴尬,不知所措。然而,如果和小于 2 人社交,他们就会觉得自己被孤立了,很有可能心生不满。所以,每个人必须恰好和 2 人社交,不多不少。

考虑到这些要求,位置的安排就非常困难了。Cuber QQ 会告诉你参加舞会的人数,想让你帮忙给一个方案。

以下是一段形式化的题目描述。

请在平面直角坐标系中放置 n 个格点 ,使得对于其中任何一个点 ,都恰好有 2 个点(不包括 i 自己)与 它 的欧几里得距离小于等于 1。你可以在同一个坐标上放置多个点。

输入格式

输入一个整数 n (1≤n≤103),表示参加舞会的人数。

输出格式

如果不存在满足条件的方案,请输出 −1。

否则,请输出 n 行,每行包含两个正整数 xi,yi(1≤xi,yi≤103),表示第 i 位朋友在舞池中的坐标。如果有多种解,输出其中任意一种即可。

样例

input

1

output

-1

input

4

output

1 1
1 2
2 1
2 2

提示

格点是指横纵坐标都是整数的点。

思路:

注意到三个数放在一起可以组成一个小团伙,四个人放一个正方形可以组成一个小团队

对1,2,5特判一下-1

然后所有数模4有四种情况:0,1,2,3

0可以全部拆成4人

1相当于(n-9)%4==0  ,拆成三个3人和一堆4人

2相当于(n-6)%4==0  ,拆成两个3人和一堆4人

3拆成一个3人和一堆4人

代码:

#include
using namespace std;
typedef long long ll;
int main(){
    int n;
    cin>>n;
    if(n==1||n==2||n==5){   //特判
        cout<<-1;
        return 0;
    }
    
    if(n%4==0){
        int cnt=n/4;
        for(int i=0;i

D. 开灯(签到)

Cuber QQ 为他的舞池准备了一条灯带,灯带上有 n 个 LED 灯。灯带的主控芯片上有 n 个非常小的按钮,分别控制每一盏灯。按下一个按钮后,对应的 LED 灯就会改变状态:由开变关,或由关变开。

初始状态下,所有的灯都处于关闭状态。舞会即将开始,Cuber QQ 想要把所有的灯都打开。可是他发现,由于按钮太小,他每次尝试去点击时,都会一下子按下连续的 m 个按钮。他想问问你,他是否能够把所有的灯都同时打开?

输入格式

输入一行两个整数 m 和 n(1≤n≤m≤106),分别表示灯的数量和 Cuber QQ 一次按下的按钮数。

输出格式

如果能点亮所有的灯则输出 Yes,否则输出 No

样例

input

10 5

output

Yes

提示

样例中,Cuber QQ 可以先按下前 5 个按钮,再按下后 5 个按钮,这样就打开了所有的灯。

思路:

灯的个数能被一次开灯的次数整除,则可以全部打开,否则总会有开不了的灯

代码:

#include
using namespace std;
typedef long long ll;
int main(){
    int n,m;
    cin>>n>>m;
    int flag=0;
    if(n%m==0)flag=1;
    if(flag)cout<<"Yes"<

E. 社恐的舞会 II(构造 / 数学)

Cuber QQ 的舞会最后搞得一团糟。他似乎高估了他的社恐朋友们的社交意愿,不少朋友们看见这一千人的大场面就被吓得不敢进门。更糟糕的是,租下这个一百万平方米的大舞池花光了 Cuber QQ 的全部积蓄。

Cuber QQ 并不甘心他的舞会计划就此失败。他痛定思痛,好好反思,终于找到了问题所在。首先,千人舞会给社恐们带来的压力还是太大了,他决定缩小舞会的规模,只邀请他最要好的 16 位朋友前来。其次,上次舞会给每位朋友安排了两位社交对象,这对于极端社恐来说仍然十分困难。所以这次,任意两人之间至少间隔两米,给这些社恐朋友们更宽松的社交距离。最后,考虑到经济问题,这次他只能找一个小一点的舞池,于是他租下了一个半径为 9.5 米的 90 度扇形舞池。因为大家不喜欢被孤立的感觉,所以每个人离舞池边缘的距离也要大于 1 米。

所以这次,你的任务是在一个半径为 9.5 米的 90 度扇形中放置 16 个互不重叠的半径为 1 的圆。为了方便各位输出答案,我们将这个扇形放入一个平面直角坐标系中,扇形的两条边分别和 x 轴与 y 轴的正方向重合。请输出这 16 个圆的圆心的坐标。

输入格式

本题没有输入。

输出格式

输出包含 16 行,每行包含两个用空格分隔的实数 xi,yi,表示每个朋友的位置。需要满足

本题答案不唯一,输出任意一组即可。

思路:

从第一层开始一层一层往上垒,按照半径1.001算,层高是根号3,如果到圆心距离大于8.5则下一层,直到数出16个圆

代码:

#include
using namespace std;
typedef long long ll;
int main(){
    int layer=1;
    vector >a;
    while(a.size()<16){
        double y=sqrt(3)*1.001*(layer-1)+1.001; //层高
        if(layer%2==1){     //奇数层
            
            for(double i=1.001;i<=9.5;i+=2.002){
                if(sqrt(i*i+y*y)<=8.5){
                    a.push_back(make_pair(i,y));
                    if(a.size()==16)break;
                }
            }
        }
        
        else{    
            for(double i=2.002;i<=9.5;i+=2.002){    //偶数层
                if(sqrt(i*i+y*y)<=8.5){
                    a.push_back(make_pair(i,y));
                    if(a.size()==16)break;
                }
            }
        }
        layer++;
    }
    
    for(auto it:a){
        cout<

G. 切蛋糕(签到 / 构造)

Cuber QQ 为 Quber CC 买了两个生日蛋糕,一个是草莓味的,另一个也是草莓味的。

Cuber QQ 把这两个蛋糕放在了餐桌上,他想只切一刀就把这两个蛋糕同时切成面积相同的两半。每个蛋糕,一半他吃,一半 Quber CC 吃。

我们可以把这两个蛋糕看作是平面直角坐标系中的两个矩形,矩形的边都平行于 x 轴或 y 轴。请找到一条直线可以同时平分这两个矩形的面积。我们将给出每个矩形一对对角的顶点的坐标来确定矩形的位置。

输入格式

输入包含两行,每行包含四个整数 x1,y1,x2,y2(−103≤x1,y1,x2,y2≤103),表示该矩形的一对对角的顶点的坐标。

输出格式

数据保证答案存在且唯一,且可以用 y=kx+b 的形式表示。

第一行请输出一个实数表示直线斜率 k。

第二行请输出一个实数表示直线的截距 b。

样例

input

0 0 2 2
-1 -1 -2 3

output

0.00000000
1.00000000

提示

保证答案直线不平行于 y 轴。如果输出的答案与标准答案的绝对或相对误差小于 10−4 我们便认为答案正确。

思路:

连接两个矩形重心即可

代码:

#include
using namespace std;
typedef long long ll;
int main(){
    double x1,x2,x3,x4,y1,y2,y3,y4;
    cin>>x1>>y1>>x2>>y2>>x3>>y3>>x4>>y4;
    double tmp1=(x1+x2)/2;
    double tmp2=(y1+y2)/2;
    double tmp3=(x3+x4)/2;
    double tmp4=(y3+y4)/2;
    double ans1,ans2;
    ans1=(tmp4-tmp2)/(tmp3-tmp1);
    ans2=tmp2-ans1*tmp1;
    cout<

 H. 套娃(CDQ分治)

Cuber QQ 想为 Quber CC 的生日准备一件礼物。他买了 n 个套娃,第 i 个套娃的长度、宽度、高度分别是 a,b,c,重量是 wi 。当且仅当 ai

Cuber QQ 想要给 Quber CC 送一个他所能组装成的最重的套娃(内部可能套了许多层),请你告诉他最重的套娃有多重。

输入格式

第一行包含一个整数 n(1≤n≤105),代表套娃的数量。

接下来的 n 行,每行包含四个整数 a,b,c,w ,表示第 i 个套娃的长度、宽度、高度。其中 1≤a,b,c≤109, 0≤w≤109。

输出格式

输出共一行,包含一个整数,表示组装成的最重的套娃有多重。

input

5
1 2 3 1
2 2 2 2
3 4 5 3
2 3 4 2
3 3 3 3

output

6

提示

在样例中,Cuber QQ 可以将第一个套娃放进第四个套娃 ,然后把第四个套娃放进第三个套娃。组装成的套娃重量是 6 。

思路:

CDQ分治,三维严格单调(类似偏序)问题

首先按照第一维排序,降一维,然后利用分治思想,将区间分为两半,各自按照第二维排序,而左区间内的第一维小于右区间的第一维,再降一维

对于第三维,我们使用树状数组进行降维,树状数组 tr[i] 表示第三维为 i 的套娃最大能装的重量,每次使用完将树状数组情况,注意要离散化处理,详见代码注释

代码:

#include 
using namespace std;
typedef long long ll;

struct ty{
	ll x,y,z;
	ll w,ans;      //ans储存里面可以套的最大重量
}a[1000005];

int n,cnt;      //cnt表示离散化后第三维c的最大值
ll tr[1000005];      //树状数组 tr[i] 储存第三维等于i的最大能装的重量

bool cmp(ty a,ty b)       //按照第一维排序
{
	if (a.x!=b.x) return a.xb.w;
}

bool cmp2(ty a,ty b){      //按照第二维排序
	if (a.y!=b.y) return a.y>1;

	if (a[mid].x==a[mid+1].x){      //保证第一维,右区间严格大于左区间

		while (a[mid].x==a[mid+1].x){
            mid--;
            if(mid==l)break;
		}
	}

	cdq(l,mid);         //前一半区间已经完全套好了

	sort(a+l,a+mid+1,cmp2);     //按第二维排序
	sort(a+mid+1,a+r+1,cmp2);

	int p=l,i=mid+1;        //双指针

	for (i=mid+1;i<=r;i++){

		while (a[p].y>n;
	set s;     //去重+离散化

	for (int i=1;i<=n;++i){         //读入数据
        cin>>a[i].x>>a[i].y>>a[i].z>>a[i].w;
        a[i].ans=0;
		s.insert(a[i].z);       //利用集合离散化
	}

	sort(a+1,a+1+n,cmp3);     //第三维排序

	int l=1;
	cnt=1;
	for (auto u:s){       //离散化,a[i].z 为离散后的第三维
		while (a[l].z==u) a[l].z=cnt,l++;
		cnt++;
	}

	sort(a+1,a+1+n,cmp);    //第一维排序降维

	cdq(1,n);	    //分治操作

	ll ans=0;
	for (int i=1;i<=n;++i)
	ans=max(ans,a[i].ans+a[i].w);       //ans储存答案

	cout<

I. 宝箱(线性dp)

Cuber QQ 的《弈!悟!》一上线就成为了爆款手游。为了留住更多的活跃用户,Cuber QQ 准备设计一些奖励机制,吸引用户每天按时登陆游戏。

Cuber QQ 推出了一场 n 天打卡活动,玩家每天登陆游戏就可获得一个红色宝箱和一个蓝色宝箱。红色宝箱和蓝色宝箱中均可开出一定数额的金币(金币数额一定是正整数)。而且随着连续打卡天数的增加,奖励会越加丰厚。在第 i+1 天的红色宝箱中开出的金额一定高于第 i 天的红色宝箱。同样地,第 i+1 天的蓝色宝箱中开出的金额一定高于第 i 天的蓝色宝箱。

核心玩家们肯定不会错过每一天的宝箱,一个全勤的玩家可以获得全部的 2n 个宝箱。Cuber QQ 为了给玩家一点惊喜,他想,这所有的 2n 个宝箱中的金币数应当互不相同;而且他想指定这其中某几个箱子一定会开出奇数数目的金币,其余的箱子一定会开出偶数数目的金币。

当然,Cuber QQ 也不想让玩家在一个箱子中获得太多的金币。他想问问你,在满足这些要求的前提下,如何安排每个宝箱开出的数额,可以使得一个玩家在单个宝箱中获得的金币的最大值尽可能小。

输入格式

第一行包含一个整数 n(1≤n≤1000),表示登陆活动的天数。

第二行包含 n 个整数 a1,a2,…,an,ai=0 表示第 i 天的红色宝箱一定开出偶数枚金币,ai=1 表示第 i 天的红色宝箱一定会开出奇数枚金币。

第三行包含 n 个整数 b1,b2,…,bn,bi=0 表示第 i 天的蓝色宝箱一定开出偶数枚金币,bi=1 表示第 i 天的蓝色宝箱一定会开出奇数枚金币。

输出格式

输出共一行,包含一个整数,表示一个玩家在单个宝箱中获得的最多的金币数的最小值。

样例

input

5
1 0 0 1 1
0 0 0 1 0

output

12

提示

在样例所述的情况中,我们可以让每天的红色宝箱分别开出 1、2、4、5、7 枚金币,每天的蓝色宝箱分别开出 6、8、10、11、12 枚金币,这样玩家在单个宝箱中最多获得 12 个金币。在其他的方案中,玩家在单个宝箱中开出的金币数的最大值都不会小于 12。

思路:

线性dp,dp[i][j]表示开了第i个红色和第j个蓝色宝箱的情况

每次用前一个宝箱更新,如果和当前宝箱奇偶相同,则用+2更新,否则用+1更新

代码:

#include
using namespace std;
typedef long long ll;
int n;
int a[1005],b[1005],dp[1005][1005];
int ans=10000000;
int main(){

    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        cin>>b[i];
    }
    memset(dp,0x3f,sizeof(dp));
    dp[0][0]=0;

    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){

            if(i>0){
                if(a[i]%2==dp[i-1][j]%2){
                    dp[i][j]=min(dp[i][j],dp[i-1][j]+2);
                }
                else dp[i][j]=min(dp[i][j],dp[i-1][j]+1);
            }

            if(j>0){
                if(b[j]%2==dp[i][j-1]%2){
                    dp[i][j]=min(dp[i][j],dp[i][j-1]+2);
                }
                else dp[i][j]=min(dp[i][j],dp[i][j-1]+1);
            }

        }
    }
    cout<

你可能感兴趣的:(算法)