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 5 0 0 1 0 0 1 2 0 2 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”,否则输出亲密值最低的一对同学的最高亲密值。
样例输入:
4 1 2 3 2 3 10 3 4 3 1 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 -1 4 10 30 20 1 2 3 4 2 5 1 3 3 10 2 1 2 2 3
样例输出:
8 5
提示:
【样例说明】
如下图,红色即为树的剩余部分。
【数据范围】
数据组数 |
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)
呵呵呵呵,第一题翻车还真的是意外。还是要小心啊。。。