新型冠状病毒(COVID19)传播 CCF程序设计实训

【问题描述】

       在大洋彼岸的 国,人们对COVID19并未引起足够重视,他们的领导人川建国同志甚至对居家隔离、戴口罩以及保持社交距离等措施非常不屑,该国疫情已经完全失控。

       在一个风景秀丽的小镇,一天早上,有 N 名晨跑爱好者(编号 1 ~ N )沿着优雅的江边景观道朝同一方向进行晨跑,第 i 名跑者从位置 Si 处起跑, 且其速度为 Vi。换句话说,对所有的实数 t ≥ 0,在时刻 t 时第 i 名跑者的位置为 Si + Vi ·t。 

       很不幸的是,其中一名跑者在 t = 0 的时刻感染了病毒,且是无症状感染者,这种病毒只会在同一时刻处在同一位置的跑者之间传播,新感染了病毒的跑者也会感染其他人,很显然,等待足够长的时间,那么病毒会感染一些特定的跑者。

       事后发现其中有一名跑者感染了新冠病毒,如果此人就是在 t = 0 时刻的那名感染者,那么,在 N 名晨跑爱好者中会有多少人感染新冠病毒?

【输入形式】

        输入包含三行:

 第一行包含为两个整数 N 和 K,分别表示运动员的人数以及开始时感染了病毒的跑者编号。

 第二行包含 个正整数 S1、S2、...、SN,用空格隔开,分别表示跑者的起始位置。

 第三行包含 个正整数 V1、V2、...、VN,用空格隔开,分别表示跑者的速度。

【输出形式】

         输出为一个整数,表示最终被感染人数。

【样例输入】

6 3

3 9 8 5 7 5

6 6 5 4 6 3

【样例输出】

3

【样例说明】
【评分标准】

     对于50%的评测用例,0< ≤ ≤102

     对于70%的评测用例,0< ≤ ≤104

     对于100%的评测用例,0< ≤ ≤107

  • 问题分析及边界条件

我第一次遇到这道题,是在考场上面。这道题拿到以后,朴素想法就是模拟时间的流逝,实时模拟各运动员的跑步情况,很容易想到,可以一秒一秒的计时,然而题目里面有一句话“对所有的实数 t ≥ 0”,这句话暗示了,时间是连续的,其实用脑子想一下就知道,相遇的时候,几乎不会是整数时间。对于两个点,看他们何时相遇,那就是算一下两个一次函数的交点,如果交点不在第一象限内,那么它们之间就不会相遇。其实这样算很麻烦,因为首先,第一次计算就是每两个点两两之间算一遍,就是C(2,n),也就是O(n^2)的复杂度,而且后面还要迭代,直到没有新的感染者产生,这样子复杂度高,代码写起来很麻烦。所以这种模拟的做法显然行不通。

模拟行不通,就去考虑搜索,动态规划,这题显然不在此列,因为,后效性什么的,显然是存在的吧,还有搜索肯定会超时。那么,凭借现在所学,那就之有找到一个规律了,看到复杂度到10^7,那么考虑O(n),O(n*log n)的复杂度,不考虑个体,而是考虑整体,这种题的要领就在于此,找到了key,编写代码就很简单了,毕竟不涉及什么复杂的数据结构和算法。

对于这种找规律的问题,我们想要一次循环,或者是循环加排序,归并,二分什么的,那就是说,需要有一个判据,我循环一次,最终被感染的人数就要出来,会不会说,有这么一个人,他可以感染很多人呢?多到什么程度,只要根据那个人的位置和速度,就可以推断出哪些人感染,哪些人不会感染。

但是现在这一群人,显然没有什么明显特征,生化母体跑得快,感染了前面的,前面的人被感染以后龟速前进,和后面赶上来的人撞上了,后面有的人又特别快,在母体感染还没有前面的人的时候,他已经超过母体被感染了,把母体前面的人都感染了。现在没有什么好的想法,只好根据人群的某些特征分类,比如说,在母体前面的分一堆,后面的分一堆,(这里还有和母体位置重叠的,那肯定一开始就感染了,这里按下不表),毕竟速度是相对的,很容易理解,两边的情况是对称的,现在一大坨变成了两坨,没有多的变数要处理(因为两边对称),反而多了一个信息,初始位置在前面或者后面。我们考虑在前面的人,什么人会尽可能地感染前面的人?那就是在后面的,速度最快的感染者,不管你前面的怎么跑,如果后面有一个速度特别快的,比前面所有人都快,那他肯定会先接触到那个母体,然后被感染,这个时候,他可以感染前面的所有人。现在有一个情况论外,有些在前面的,跑的特别慢的,直接在开始的时候,就被母体超了,导致那个快的先接触的那些慢人,不是那个母体,这其实也没有影响,因为母体都已经接触过慢人了,他们成小母体了。这个时候又有新的疑问?那些小母体,会不会到时候扫描的时候,没有被统计到呢?这一点不用担心,我们从后面找的人,他的速度绝对是大于等于母体的速度的,就算拿母体的速度做判断依据,那些慢的人也会被统计到的,我现在放缩了,搞了个更高的速度,就是为了涵盖到更多的潜在客户,前面的小客户肯定不在话下了。

我们想一想,哪些人不会被感染,根据题目所给的参数,路程和速度,我们可以看出,那些站得在母体很前面,速度还很快的人,不会被感染,你离得远,速度还快,马上就到前面去了,不用管后面的人怎么样了。还要一种人,站得离母体远,速度还很慢,别人都跑远了,你在后面优哉游哉地散步,那前面的人干嘛,和你没有半毛钱关系。只有那些高不成低不就的,要在泥潭里面打滚。

通过思考问题的背面,我们就可以想到这道题的评判依据了,我先列个表。

速度\距离

后面

前面

没事

有事

有事

没事

如果我们把生化母体的速度和位置设为原点,后面和前面是拿初始感染者的位置确定的,我们要做的只有区分快慢了。想要找前面的人有多少人感染,那得在后面搞一个尽量快的人,同样地,要想后面感染的人尽量多,我得在前面找一个尽量慢的人,如果母体的速度就已经是尽量快或者慢的了,那么那个基准的速度,很巧,那就是母体本人的速度了,不过通常,那个边界速度需要被迭代。

我们现在已经几乎做出了这个题,因为我们先根据特征缩小问题规模,根据事物的普遍性和辩证思想,找到了规律和边界条件,并且通过很简单的方法,就可以筛出被感染的人。似乎这道题已经大功告成,可是我交上去,发现怎么都只对了一个点,为什么呢?是因为我没有考虑初始感染者和别人重合的情况,没有考虑那些重合的人也会被感染,并且因此改变边界条件,所以说,虽然答题思路对了,但是边界条件和人数一个没对。这在考场上通常不太好,因为就是比较简单的东西,自己要花很长时间才能想到,所以说那个时候只能多造几组比较特殊的数据来尝试了,然后手算一遍,没别的办法。

这里先讲一下快速读取吧,这题数据量大,正好用快速读取。读取速度cin<scanf<getchar<fread,正常情况,遇到大数据输入,关缓冲区,解除ios::sync_with_stdio(false)就可以了,遇到极端一点的,就用scanf,这个要熟悉一下数据类型对应的代码。之后就是拿getchar做读取了。其实getchar就是一个字符一个字符地读入,遇到’\0’或’\n’结束,这个题输入的全是数字,读取结束的条件,就是这个字符的ASCII码不在0-9内。因为是从左往右读入,数字的大小是从右往左读的。因为是十进制,原来读入的数字,要*10再与新读入的那位数字相加,这样就达到了从左往右读入的效果了,等效于十进制向左移动一位。加法减法的效率不行,我们换成位运算,x*10就是x*2+x*8,就是(x<<1)+(x<<3),要打括号,<<的优先级好像有点低,字符转数字是ch-‘0’(48),减法和异或是等价的。

一些题外话:

咱们先看不进位加法,异或和不借位减法运算表:

+

0

1

0

0

1

1

1

0

^(异或)

0

1

0

0

1

1

1

0

-

0

1

0

0

1

1

1

0

一模一样,这个运算加个作用域{0,1},变成代数系统,可不可以说他们同构呢?这是个离散数学问题,但是我把离散数学给忘了。

二进制也是要借位的,可以看出,不进位加法,不借位减法就是异或!那怎么才能用异或实现不进位加法呢?那就是找到要进位的位数,然后左移1位,其实我们把要加的两个数a&b,都是1的才为1,那也就是说,只有那些数,才是需要进位的,我们之前的异或,少算了那些数字,那该怎么办,那就把之前得到的sum1和进位得到的sum2相加,然后递归地执行这个过程,直到加数为0为止,这就是二进制的加法。

之后我们扫描的时候就很简单了,先扫描位于母体后面,速度最快的人,目的是筛选母体前面被感染的人,同理,也扫描位于母体前面,速度最慢的人,这是对称性导致的。之后把囊括在这两个区间里面的人找出来,统计人数即可。

有了上面的规律,很容易用人眼得出,3号生化母体,位置是8,左边的最快的是6,也就是说,8右边的,速度比6小的都要遭,那么9速度是6,那就没有人遭。看右边最慢的位置8速度5,8左边的,速度比5大的遭殃,(3,6),(7,6)两个遭。加上本来的,所以最后答案是3个人。

6 3

3 9 8 5 7 5

6 6 5 4 6 3

其实,在真正考虑这个问题的过程中,如果很久都想不出朴素做法或者是高技巧解法,那些细节,其实也是应该最后再考虑,先按着大体思路来做,至少要整出一个可能正确的骨架。之后再后验这个思路的正确性,很难在一定的时间内把题目想清楚。如果在考试的时候,可以多举例子验证,在练习的时候,再去想想为什么吧,这个还是有点耗费时间的。还是得多练,才能快速找到正确的思路,因为在考试的时候,能够快速找到思路,都是前面的思路拼起来的,也就是所谓的经验,灵光乍现需要长时间的积累和过人的天赋。由于时间有限,还是先做那些看得到影子的题目吧。不过我也相信,有的时候,也是会灵光乍现的,哈哈,要是能在不知情的情况下,造出一种主流算法或者主流数据结构,那个成就感是爆棚的。

#include <iostream>
#include <iomanip>
#include <vector>
#include <unordered_map>
#include <map>
#include <cstring>
#include <cmath>
#include <queue>
#include <list>
#include <unordered_set>
#include <set>
#include <algorithm>
#include <sstream>

using namespace std;
char buf[1<<21];
char *p1=buf,*p2=buf;
char bufp[1<<21];
char* p3=bufp;
inline char gc(){
	if(p1==p2){
		p2=buf+fread(buf,1,1<<21,stdin);
		p1=buf;
	}
	return *(p1++);
}
inline void pc(char ch){
	if(p3-bufp==1<<21){
		fwrite(bufp,1,1<<21,stdout);
		p3=buf;
	}
	*(p3++)=ch;
}
inline int reader(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void writer(int x) {
  static int sta[35];
  int top = 0;
  do {
    sta[top++] = x % 10, x /= 10;
  } while (x);
  while (top) putchar(sta[--top] + 48);  // 48 是 '0'
}
int main(){
	int n=reader(),k=reader();
	//getchar();
	k-=1;
	vector<int> s,v;
	for(int i=0;i<n;i++){
		int t;t=reader();s.push_back(t);
	}
	//getchar();
	for(int i=0;i<n;i++){
		int t;t=reader();v.push_back(t);
	}
	int sk=s[k],vmin=v[k],vmax=v[k];int res=0;
	for(int i=0;i<n;i++){
		if(s[i]<sk&&v[i]>vmax){
			vmax=v[i];
		}
		else if(s[i]>sk&&v[i]<vmin){
			vmin=v[i];
		}else if(s[i]==sk){
			res++;
			if(v[i]>vmax){
				vmax=v[i];
			}
			if(v[i]<vmin){
				vmin=v[i];
			}
		}
	}
	for(int i=0;i<n;i++){
		if(v[i]<vmax&&s[i]>sk){
			res++;
		}
		else if(v[i]>vmin&&s[i]<sk){
			res++;
		}
	}
	writer(res);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值