首页 > 代码库 > 2017.8.9的考试——脑抽的日常

2017.8.9的考试——脑抽的日常

T1

基因光线 light

题目描述:

黑大帅统治古古怪界后,一直在玩一种很奇葩的游戏。在一个二维平面上,他先复制了n个小A,把他们放在不同的位置,然后射出一条ax+by+c=0的基因光线,宽度为d,即离这条直线的距离不大于d的小A会被射中。当然,某些悲剧的小A就会被射中,并变成黑小A。当然,这不是重点。玩了很久后,黑大帅猛然发现,自己竟然一次都没有射中小A。黑大帅怒了,于是他开启了作弊模式,将c改成自己想要的任意数值。现在,黑大帅想知道,在开启了作弊模式后,他射出一道基因光线最多能击中几个小A。

输入格式:

第一行五个数字a,b,d,n,接下来n行每行两个数字x,y表示这个小A的坐标。

输出格式:

一行一个数字表示最多能击中几个小A。

样例输入:

1 -1 0.707106782 50 01 00 12 02 1

样例输出:

4

提示:

【样例说明】

       将c值改为0或-1可以击中4个小A,可以证明不可能同时击中5个小A。

【数据范围】

       50%的数据满足a=0;

       100%的数据满足n<=100000,其余所有数值均为绝对值不大于1000的实数。

时间限制:1000ms
空间限制:256MByte

这个题目是不是看起来可以AC,啊哈!我也是这么想的。然后就马不停蹄地写出了一下代码

#include<bits/stdc++.h>using namespace std;int a,b,n,k,dp[1000005]={0},maxn=0;double d;struct nob{	int x,y;	double len;}s[100001];bool mmp(nob a,nob b){	return a.len<b.len;}int main(){	freopen("light.in","r",stdin);	freopen("light.out","w",stdout);	cin>>a>>b>>d>>n;	k=-1.0*a/b;	for (int i=1; i<=n; i++){		cin>>s[i].x>>s[i].y;		if (s[i].y>=s[i].x*k)		s[i].len=abs((a*s[i].x+b*s[i].y)*1.0/sqrt(a*a+b*b));		else s[i].len=-1.0*abs((a*s[i].x+b*s[i].y)*1.0/sqrt(a*a+b*b));		dp[i]=1;	}	sort(s+1,s+1+n,mmp);	for (int i=1; i<=n; i++){		for (int l=i-1; l>=1; l--){			if (s[i].len-s[l].len>d) break;			else dp[i]++;		}		for (int l=i+1; l<=n; l++){			if (s[l].len-s[i].len>d) break;			else dp[i]++;		}		maxn=max(maxn,dp[i]);		if (maxn==n){			cout<<maxn;			return 0;		}	}	cout<<maxn;	return 0;}//d=abs((ax0+by0+c)/sqrt(a*a+b*b));点到直线的距离公式

然后就爆零了!呵呵呵呵呵。之后发现自己的一个思维错误,在上述程序中,我是以一个点为直线的中点,然后再以宽度d向上下两边延伸。但是!大部分时候!直线中点不在点上时能够包括更多的点!所以应该以点为直线的底边向上延伸2d距离。然后为了提高效率,在向上延伸的时候,可以用二分查找来缩短时间!但是你不用写二分就可以拿九十分!是不是很赚!以下是呆滞大佬的标程。

#include<bits/stdc++.h>using namespace std;const int maxn=100005;double a,b,d,x,y;int n;struct dt{	double h;}e[maxn];bool cmp(dt p,dt q){	return p.h<q.h;}int main(){	freopen("light.in","r",stdin);	freopen("light.out","w",stdout);	int ans=0;	int head,tail,mid;	double t;	scanf("%lf%lf%lf%d",&a,&b,&d,&n);	t=sqrt(a*a+b*b);	for(int i=1;i<=n;i++){		scanf("%lf%lf",&x,&y);		e[i].h=(a*x+b*y)*1.0/t;	}	sort(e+1,e+1+n,cmp);	d=2*d;	for(int i=1;i<=n;i++){		head=i;tail=n;		while(head!=tail){			mid=(head+tail)>>1;			if(head==mid)				mid++;			if(e[mid].h<=(e[i].h+d))				head=mid;			else				tail=mid-1;		}		if((head-i+1)>ans) ans=head-i+1;	}	printf("%d\n",ans);	return 0;}

接下来是题解的解释。

【基因光线】

       当c固定时,被击中的小A必满足|ax+by+c|/sqrt(a^2+b^2)<=d,即

|ax+by+c|<=d*sqrt(a^2+b^2),令D=d*sqrt(a^2+b^2),则有|ax+by+c|<=D,则,

-D<=ax+by+c<=D,即-D-c<=ax+by<=D-c,令R=ax+by,则-D-c<=R<=D-c,则问题转换为,在一维坐标轴上,小A的坐标为R,用2D长度最多覆盖几个小A。把小A按R从小到大排序,枚举作为开头的小A,用一个指针维护末尾的小A,不断更新答案,算法复杂度为O(nlogn),可以通过全部数据。

T2

好朋友 friend

题目描述:

noip2017就要来了,W校的同学们不仅看重这次比赛,更看重noip2011和谁住在同一个房间。同学之间的关系好坏可以用一个亲密值表示,亲密值越大,两个同学关系越好。小A作为W校信息组的组长,自然想要让同学们在比赛前能好好休息,放松心情,让同学们在赛场上能够超常发挥。他现在知道自己预订的房间都是双人间,且知道这n个同学之间的关系。n个同学的关系可以用一个n条双向边的连通图来描述,即某个同学只愿意和与他有边相连的同学住同一个房间,边权即为两个同学的亲密值。数据保证没有重边、自环。现在小A想知道在让所有同学的要求满足的情况下,亲密值最低的一对同学亲密值最高是多少。

输入格式:

第一行一个正整数n,下面n行每行三个数u,v,w,表示u到v有一条边权为w的双向边。

输出格式:

假如无论如何都无法满足所有同学的要求,输出”no answer”,否则输出亲密值最低的一对同学的最高亲密值。

样例输入:

41 2 32 3 103 4 31 4 1

样例输出:

3

提示:

【样例解释】

       有两种选择。一种选择是<1,2>、<3,4>,最低亲密值为3;另一种选择是<1,4>、<2,3>,最低亲密值为1。所以最高的最低亲密值为3。

【数据范围】

       50%的数据满足n<=20;

       80%的数据满足n<=1000;

       100%的数据满足n<=100000,-10^9<=w<=10^9

时间限制:1000ms
空间限制:256MByte

这题题目以我现在的水平就只能水水分,因为。。。我根本就不知道什么是双联通分量。

以下是题解。

【好朋友】
首先根据这道题的题意可以直接搜索求解。假如u未匹配,每次在u+1..n找一个未匹配的且与u有边相连的点,递归操作。中间可以加入最优化剪枝,可以通过零接表再加快速度,对于n<=60的数据都可以轻松通过。算法复杂度O(n!),可以通过50%的数据。
仔细观察题目发现,这是一种特殊的图。众所周知n个点n-1条边的连通图是一棵树。
假设原图是一棵树,任选一点作为根,那么叶子节点u必是与他的父亲father[u]相匹配,假如father[u]已被匹配,那么必然无解;否则father[u]标记为已匹配,继续递归,规模减2,这样我们就有了一个O(n)的贪心算法。
n个点n条边的连通图就是在树上加一条边得到的图,有且仅有一个环。我们可以先找到这个环,环外的部分必然是树,我们可以通过上述贪心实现,然后再处理这个环就行了。
假如环上已经有点被匹配了,那么环一定被断成了一段一段的链。链也是树的一种,同样可以贪心解决;假如环上所有的点都没有被匹配,这个环必然是含偶数个节点的环,直接考虑下列两种方案,取最优即可。

找环有许多方法。O(n^2)的方法是直接暴力枚举每个点作为根,dfs一遍看是否有返祖边与根相连,找出所有环上的点。定一个环上的点作为开头,寻找下一个与他相连的环上的点,以此推出顺序。
下面再介绍三种O(n)找出图的唯一环的方法:
(1) 直接找出所有的双联通分量,其中大小不等于1的即是所求的环。然后再用上述方法求出顺序即可;
(2) 任选一点作为根,建立一棵生成树,剩下的一条边即为环边。不妨设这条边为<u,v>那么v到lca(u,v)经过的点,u到lca(u,v)经过的点即为环上的点,并直接得到顺序。(lca(u,v)表示u,v的最近公共祖先)
(3) 任选一点dfs,dfs过程中给边定向。假如某点在dfs时第2次经过,那么这个点必然是环上的点,一直退栈直到第一次经过这个点,中间的所有点均为环上的点,同时也得到了顺序。

T3

砍树 cut

题目描述:

小A是小B家的园丁。小B的家里有n棵树,第i棵树的横坐标为i。一天,小B交给小A一个任务,让他降低自己家中的某些树木的高度。这个任务对小A来说十分简单,因为他有一把极其锋利的斧头和一门独门砍树秘籍,能够轻易地砍断任何参天大树。小A的砍树方法有3种,都是沿着一条y=kx+b的直线砍一段区间的树,相同的方法k值相同。只用了一个下午,小A就完成了小B的任务。第二天,小B来视察小A的任务完成情况。小B想知道小A是否真的用心砍树,于是提出了q个询问,每次询问一段区间中最低的树的高度。小A当然是不会记住树木的砍伐情况的,他只知道自己按什么顺序,使用了什么方法,砍了哪个连续区间的树,而且区间都是互不包含的。现在小A想请你帮帮他,回答小B的询问。

输入格式:

第一行三个整数k1,k2,k3表示小A三种砍树方法的斜率值;

第二行一个整数n,表示一共有n棵树;

第三行n个数整数hi,分别表示n棵树的高度;

第四行一个整数m,表示小A一共进行了m次操作;

接下来m行,每行四个数L,R,p,b,表示用第p种方法,即用y=kp+b的直线砍[L,R]区间的树;

接下来一行一个数q,表示小B的询问数;

接下来q行,每行两个数L,R,表示询问[L,R]区间中最低的树的高度。

输出格式:

一共q行,每行一个数h表示对应的回答。

样例输入:

1 0 -1410 30 20 123 4 2 51 3 3 1021 22 3

样例输出:

85

提示:

【样例说明】

       如下图,红色即为树的剩余部分。

技术分享

【数据范围】

数据组数

n

m

q

1-2

1000

500

1000

3

50000

20000

1

4

50000

1

50000

5-6

50000

30000

50000

7-10

1000000

500000

500000

时间限制:3000ms

空间限制:256MByte

离线做法?RMQ?单调队列?水吧……水吧……以下是题解。

【砍树】
首先,这道题目可以分成两部分:砍树和询问的部分。
对于砍树的部分,我们可以暴力做,这样的复杂度是O(nm)的,能通过20%的数据。
观察直线的解析式我们发现,对于同个k、同个x,kx恒定不变,所以影响某棵树高度的直线只能是经过这棵树的直线中b值最小的那条。这样我们可以把三种砍树方式分开做,然后按b值从小到大排序,然后用染色的方法做。这里可以用线段树或并查集来染色,总效率O(mlogm+n),可以通过60%的数据。
题目中有一个重要的条件就是这些砍树区间互不包含,这样我们就可以使用单调队列的方法。按左端点计数排序,扫描,维护一个区间右端点递增,b值递增的单调队列。由于每个点出入队列最多一次,所以总效率为O(n+m)可以通过100%的数据。
经过前面一部分,其实我们已经知道了每棵树的最终高度。
对于询问的部分,我们同样可以暴力做,复杂度为O(nq),可以通过20%的数据。
这其实是一个经典的区间最小值的问题,可以用线段树或RMQ在线解决,总效率O(qlogn+n)或O(nlogn+q),可以通过60%的数据。
这道题目并没有修改操作,只是单纯的询问,所以我们考虑离线做法。将询问区间按右端点计数排序。扫描n棵树的过程中维护一个位置递增,h值递减的单调栈。当经过的点是某个询问中的右端点时,二分左端点位置,得到最小值,总效率为O(n+qlogn),但对于随机数据,单调栈是很小的,所以可以通过80%的数据。
考虑到现在算法的瓶颈在于二分,我们可以利用并查集,动态维护每个点往右最近的且在单调栈中的点,总效率O(n+q),可以通过100%的数据。
下面是对于数据[9,8,9,1]的做法:

1. 指针:0
栈:(空)
2. 指针:1
栈:1(9)
3. 指针:2
1退栈,2进栈,1连2;
栈:2(8)
4. 指针:3
栈:2(8),3(9)
5. 指针:1
3退栈,3连4,2退栈,2连4;
栈:4(1)

呵呵呵呵,第一题翻车还真的是意外。还是要小心啊。。。

2017.8.9的考试——脑抽的日常