2019 HL暑假集训 Day 3

本篇博客记录了2019年暑假编程集训的第三天,涵盖了3个编程题目:三笔画问题、放积木问题和取石子问题。对于三笔画问题,介绍了通过统计不同y坐标上的点来判断是否能用最多3条直线覆盖所有点。放积木问题采用贪心策略解答,通过倍增优化处理大数据。而取石子问题则涉及到了游戏策略分析,对四种不同情况的解决方案进行了讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目按难度从简到难手动排序————题记

T1:三笔画(3lines)

【题目描述】:

二维平面内有 n 个不同的点, Alice 需要在平面内画至多 3 条直线使得所有点在直线上。
问: Alice 能否完成任务, 如果能, 输出”YES”; 否则, 输出”NO”。
注意: 由于 Alice 的画图水平有限, 直线只能平行于坐标轴。

【输入数据】:

第一行,一个整数 n。
接下来 n 行,第 i+1 行包含空格隔开的整数 xi,yi,表示第 i 个点的坐标。

【输出数据】:

若 Alice 能完成任务, 输出”YES”, 否则输出”NO”。

【样例输入】:

6
1 7
0 0
1 2
2 0
1 4
3 4

【样例输出】:

YES

【样例解释】:

三条直线分别为 x=1,y=0,y=4。

【数据范围】:

对于 30%的数据,1 <= n <= 13。
对于 60%的数据,1 <= n <= 20。
对于 100%的数据,1 <= n <= 5e4,0 <= xi, yi <= 1e9。


心路历程:对于这道题,在考场上我思考了很久,经过分析,我发现如果在一条线上有超过3个点,说明必须要使用一条线去覆盖这几个点,就使Line−1Line-1Line1LineLineLine初始值为3),将这几个点删去。如果没有这种情况,那么就暴力枚举剩余Line条线,判断是否可以覆盖。对于大数据,这种方法很有效,对于小数据,那就直接暴力吧!【φ(>ω<*)】
正解:对于三条直线的状态,只有以下2种状态:

  • 1.1.1.三条水平线
  • 2.2.2.两条水平线+一条垂直线
    (剩余情况旋转坐标轴即可)
    我们用一个数组统计同一 yyy 坐标上有几个点。
    对于 ① 的情况,只需判断是否只有三个及以下的 yyy 坐标上有点即可。
    对于 ② 的情况,可以枚举垂直线的 x 坐标,将这条垂直线上的点全部删去,判断剩下的点的 yyy 坐标是否只有两种及以下。
    因为xi,yixi,yixi,yi有些大,可以先进行离散化预处理后再进行操作。
    将点按 xxx 坐标排序后即可做到 O(n)O(n)O(n)的扫描.
非正解代码如下:
#include <bits/stdc++.h>

using namespace std;
const int N = 600000;

int n;
int x[N], y[N];

bool check(int p1,int t1,int p2,int t2,int p3,int t3)
{
	int ans = 0;
	for (int i=1;i<=n;++i) {
		if (x[i] == p1 && t1 == 1) ans ++;
		else if (x[i] == p2 && t2 == 1) ans ++;
		else if (x[i] == p3 && t3 == 1) ans ++;
		else if (y[i] == p1 && t1 == 2) ans ++;
		else if (y[i] == p2 && t2 == 2) ans ++;
		else if (y[i] == p3 && t3 == 2) ans ++; 
	}
	return ans == n;
}

bool work1(void)
{
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
			for (int k=1;k<=n;++k) {
				if (check(x[i],1,x[j],1,x[k],1)) return 1;
				if (check(x[i],1,x[j],1,y[k],2)) return 1;
				if (check(x[i],1,y[j],2,x[k],1)) return 1;
				if (check(x[i],1,y[j],2,y[k],2)) return 1;
				if (check(y[i],2,x[j],1,x[k],1)) return 1;
				if (check(y[i],2,x[j],1,y[k],2)) return 1;
				if (check(y[i],2,y[j],2,x[k],1)) return 1;
				if (check(y[i],2,y[j],2,y[k],2)) return 1;
			}
	return 0;
}

bool work2(void)
{
	map <int,int> cnt1;
	map <int,int> cnt2;
	map <int,int> cnt3;
	map <int,int> cnt4;	
	int cnt_x = 0, cnt_y = 0, P = -1, T = -1;
	for (int i=1;i<=n;++i) 
		cnt1[x[i]] ++, cnt2[y[i]] ++;
	for (int i=1;i<=n;++i) 
	{
		if (cnt1[x[i]] > 3) {
			P = x[i], T = 1;
			break;
		}
		if (cnt2[y[i]] > 3) {
			P = y[i], T = 2;
			break;
		}
	}
	if (P == -1 && T == -1) return 0;
	for (int i=1;i<=n;++i)
	{
		if (x[i] == P && T == 1 || y[i] == P && T == 2) continue;
		cnt3[x[i]] ++;
		if (cnt3[x[i]] == 1) cnt_x ++;
		cnt4[y[i]] ++;
		if (cnt4[y[i]] == 1) cnt_y ++;
	}
	return cnt_x <= 2 || cnt_y <= 2;
}

int main(void)
{
	freopen("3lines.in","r",stdin);
	freopen("3lines.out","w",stdout);
	scanf("%d", &n);
	for (int i=1;i<=n;++i) 
		scanf("%d %d", x+i, y+i);
	if (n <= 20) work1() ? puts("YES") : puts("NO");
	else work2() ? puts("YES") : puts("NO");
	return 0;
}
正解代码(不是我写的):
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
struct point{
	int x,y;
}a[50001];
int xx[50001];
int yy[50001];
int sum[50001];
int fx,fy;
int read(){
	scanf("%d",&n);
	for (int i=1;i<=n;++i){
	    scanf("%d%d",&xx[i],&yy[i]);
		a[i].x=xx[i];
		a[i].y=yy[i];		
	}   
	sort(xx+1,xx+n+1);
	sort(yy+1,yy+n+1);
	fx=unique(xx+1,xx+n+1)-xx-1;
	fy=unique(yy+1,yy+n+1)-yy-1;
	for (int i=1;i<=n;++i){
		a[i].x=lower_bound(xx+1,xx+fx+1,a[i].x)-xx;
		a[i].y=lower_bound(yy+1,yy+fy+1,a[i].y)-yy;
	}
}
int cmp(const point &a,const point &b){
	return a.x<b.x?1:((a.x==b.x)&&(a.y<b.y));
}
int work(){
	int tot=0;
	sort(a+1,a+n+1,cmp);	
	for (int i=1;i<=n;++i)
	    if (sum[a[i].y]++==0)++tot;
	if (tot<=3)return 1;
	int t=1,w;
	while (t<=n){
		w=t;
		while (w<n&&a[w+1].x==a[t].x)++w;
		for (int i=t;i<=w;++i)
		    if (sum[a[i].y]--==1)--tot;
		if (tot<=2)return true;
		for (int i=t;i<=w;++i)
		    if (sum[a[i].y]++==0)++tot;
		t=w+1;
	}
	return 0;
}
int main(){
	freopen("3lines.in","r",stdin);
	freopen("3lines.out","w",stdout);
	read();
	if (work()){
		puts("YES");
	}
	else {
		memset(sum,0,sizeof(sum));
		for (int i=1;i<=n;++i)
		    swap(a[i].x,a[i].y);
		puts(work()? "YES" : "NO");
	}
	fclose(stdin);
	fclose(stdout);
}

T2 放积木(block)

【问题描述】:

AliceAliceAlicennn 块积木,放置第iii块积木会占据区间[Li,Ri][Li, Ri][Li,Ri]
AliceAliceAlice 每次会腾出一个区间放积木,她希望放的积木尽可能多,对每个询问区间,你需要回答 AliceAliceAlice 最多可放置的积木数量。
注意: 积木与积木的放置区间不可重叠,且任意选定的积木放置区间不能超出询问区间。
lenlenlen指木块的最长区间长度。

【输入格式】:

第一行 三个整数 n,q,lenn,q,lenn,q,len,表示积木的数量,询问数和 lenlenlen 的大小(数据保证 1≤Li,Ri≤len1≤Li,Ri≤len1Li,Rilen)。
接下来 nnn 行,每行两个整数Li,RiLi,RiLi,Ri,表示积木的区间。
接下来 qqq 行,每行两个整数ai,biai,biai,bi,表示 AliceAliceAlice 选定的区间。

【输出格式】:

对于每组询问输出对应的答案。

【输入样例】:

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

【输出样例】:

1
0

【数据范围】:

对于 303030%的数据满足 n<=10,q<=10n <= 10, q <= 10n<=10,q<=10
对于 606060%的数据满足 n<=1,000,q<=1,000,1<=Li,Ri<=1,000n <= 1,000, q <= 1,000, 1 <= Li, Ri <= 1,000n<=1,000,q<=1,000,1<=Li,Ri<=1,000
对于 100100100%的数据满足 n<=1e5,q<=1e5,1<=Li,Ri<=len<=1e5n <= 1e5, q <= 1e5, 1 <= Li, Ri <= len <= 1e5n<=1e5,q<=1e5,1<=Li,Ri<=len<=1e5


心路历程:对于这道题,一看完题,就知道这是一个裸的贪心(先按右端点排序,然后再从左到右在询问区间中放入积木,最后的出的一定是最优解),再看数据范围,直接暴力贪心可得60分,这是一个令我比较接受的分数,再加上我思考了一会儿没有相处优化的方法,就直接打上了暴力贪心,评测结果也得到了60分。
正解:没错,这道题就是裸的贪心,但是如何优化呢?答案是:倍增
f[i,j]f[i,j]f[i,j]表示从位置 i 开始,选择 2j2^j2j 条线段, Ri 最大的线段 Ri 最小是多少。显然所有的f[i,j]f[i,j]f[i,j]可以在 O(nlogn)O(nlogn)O(nlogn)的时间内计算出来 (lenlenlennnn 同级)。
询问时我们从xxx开始,从大到小枚举kkk,如果选择2k2^k2k条线段后没有超出区间的范围,那么答案加上 2k2^k2k,然后继续统计f[x,k]+1f[x,k]+1f[x,k]+1 ~ yyy这段区间的答案。这样单次询问复杂度是 O(logn)O(logn)O(logn)的,询问的总复杂度是 O(Qlogn)O(Qlogn)O(Qlogn),可以解决这道题。

正解代码:
#include <bits/stdc++.h>
using namespace std;

const int N = 200001, M = 30;
int n, m;
struct edge{
	int l, r;
}a[N];
int q, len;
int fa[N][M] = {};

bool cmp(edge x,edge y) {
	return x.r < y.r;
}

int main() {
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	scanf("%d%d%d",&n,&q,&len);
	for (int i=1;i<=n;i++)
		scanf("%d%d",&a[i].l,&a[i].r);
	sort(a+1,a+n+1,cmp);
	memset(fa, 20, sizeof(fa));
	for (int i=1;i<=n;i++)
		fa[a[i].l][0] = min(fa[a[i].l][0], a[i].r);
	for (int i=len;i>=1;i--)
		fa[i][0] = min(fa[i][0], fa[i+1][0]);
	for (int j=1;(1<<j)<len;j++)
		for (int i=1;i<=len;i++)
			if (fa[i][j-1] <= len) fa[i][j] = fa[fa[i][j-1]+1][j-1];
	for (int i=1;i<=q;i++) {
		int x, y;
		int ans = 0;
		scanf("%d%d",&x,&y);
		for (int j=25;j>=0;j--)
			if (fa[x][j] <= y) x = fa[x][j] + 1, ans += 1<<j;
		printf("%d\n",ans);
	}
	return 0;
}

T3:取石子(stone)

【题目描述】:

nnn 堆石子,第iii 堆有 xixixi 个。
AliceAliceAliceBobBobBob 轮流取石子(先后手未定),Alice 每次从一堆中取走 aaa 个,BobBobBob 每次从一
堆中取走bbb 个,无法操作者输。
不难发现只会有四种情况:AliceAliceAlice 必胜;BobBobBob 必胜;先手必胜;后手必胜。
你需要选定若干堆石子(共有 2n2^n2n 种方案),AliceAliceAliceBobBobBob 只能在你选出的堆中取,问
以上四种情况对应的方案数。对 109+710^9+7109+7 取模。

【输入数据】:

第一行三个整数 n,a,bn,a,bn,a,b,第二行 nnn 个整数x1x1x1 ~ xnxnxn

【输出数据】:

一行四个整数,分别表示AliceAliceAlice 必胜、BobBobBob 必胜、先手必胜和后手必胜的方案数,对
109+710^9+7109+7 取模。

【样例输入】:

2 2 3
2 3

【样例输出】:

2 0 1 1

【样例解释】:

选定空集时后手必胜, 选定{2}时 AliceAliceAlice 必胜, 选定{3}时先手必胜, 选定{2,3}时 AliceAliceAlice 必胜。

【数据范围】:

对于 101010%的数据,n,xi<=5n, xi <= 5n,xi<=5
对于 505050%的数据,n<=20n <= 20n<=20
对于另外 101010%的数据,a=ba = ba=b
对于又另外 202020%的数据,a=1a = 1a=1
对于 100100100%的数据,1<=n<=1e5,1<=a,b,xi<=1e91 <= n <= 1e5, 1 <= a, b, xi <= 1e91<=n<=1e5,1<=a,b,xi<=1e9

心路历程:看完题目,果断放弃,并在考试之前未再看此题(除了打刷分程序)【(´Д`)y-~】
事实证明我是对的,我现在还未将正解写出来。。。。【( ̄ω ̄;)】
正解

  • 不妨假设a<ba < ba<b
  • 每堆石子先对a + b取模,然后可以分为4种:
  • xi<axi < axi<a,没用。
  • a<=xi<ba <= xi < ba<=xi<b,只要存在则a必胜。
  • b<=xi<2ab <= xi < 2ab<=xi<2a,只和奇偶性有关。
  • 2a<=xi2a <= xi2a<=xi, 存在至少222个则aaa必胜, 存在111个且③为偶数则先手必胜, 存在111个且③为奇数则aaa必胜, 不存在且③为奇数则先手必胜, 不存在且③为偶数则后手必胜。
    时间复杂度:O(n)O(n)O(n)
代码 :Loding… … …
内容概要:本文档详细介绍了基于MATLAB实现多目标差分进化(MODE)算法进行无人机三维路径规划的项目实例。项目旨在提升无人机在复杂三维环境中路径规划的精度、实时性、多目标协调处理能力、障碍物避让能力和路径平滑性。通过引入多目标差分进化算法,项目解决了传统路径规划算法在动态环境和多目标优化中的不足,实现了路径长度、飞行安全距离、能耗等多个目标的协调优化。文档涵盖了环境建模、路径编码、多目标优化策略、障碍物检测与避让、路径平滑处理等关键技术模块,并提供了部分MATLAB代码示例。 适合人群:具备一定编程基础,对无人机路径规划和多目标优化算法感兴趣的科研人员、工程师和研究生。 使用场景及目标:①适用于无人机在军事侦察、环境监测、灾害救援、物流运输、城市管理等领域的三维路径规划;②通过多目标差分进化算法,优化路径长度、飞行安全距离、能耗等多目标,提升无人机任务执行效率和安全性;③解决动态环境变化、实时路径调整和复杂障碍物避让等问题。 其他说明:项目采用模块化设计,便于集成不同的优化目标和动态环境因素,支持后续算法升级与功能扩展。通过系统实现和仿真实验验证,项目不仅提升了理论研究的实用价值,还为无人机智能自主飞行提供了技术基础。文档提供了详细的代码示例,有助于读者深入理解和实践该项目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值