文章标题
2015-05-22 19:47
344 查看
A.B。a。g。g。a。g。e(L。A。 6。7。7。0)
注:此题题解里所有的图片来自youtube上的官方题解https://www.youtube.com/watch?v=0lpoNGUc8pU题目链接
https://icpc.kattis.com/problems/baggage题目大意
给你n个蓝色箱子’B’和红色箱子’A’,初始时这2n个箱子以BABABA…顺序排列在一排,最左边的B号箱子放在1号格子上。现在每次操作可以将相邻的两个箱子一起移动到其他地方。问最少要多少次操作,才能使2n个箱子以AAABBB顺序紧紧挨着排列在一起(最后这些箱子放在什么位置无所谓)
思路
考虑n=4的情况,可以构造出一种方案最终这8个箱子和原来它们的位置相比,往左平移了2个格子
事实上,我们可以找到规律:假如有n对AB箱子,那么最少只需要n步就可以完成排序任务!而且对于n>7的情况,都可以从n=3,4,5,6的情况推出(即n的方案可以从n-4的方案推出),如下
首先,保留从5号格子到2n-4号格子里的所有箱子不变,2n-2、2n-1号格子里的箱子移动到-1,0号格子里,然后把3,4号格子里的箱子移动到之前留下的两个空格中,有意思的是,这样操作就把左边的四个箱子向左移动2个单位,留出两个格子
上一轮操作在5号格子左边留下了两个空格,为中间部分箱子的排序做了准备,因此我们对从5号格子到2n-4号格子的部分进行排序就可以了,这里直接用n-4时的排序方案即可,这样排序后在2n-4号格子右边留下了两个空格
然后把最左边的2个连续的B箱子移动到2n-4号格子右边的两个空格里,最右边的两个A箱子移动到0号和1号格子里,排序完成
实际上就是(n-4)+4=n步
后记:这个题也太尼玛坑了,全是手玩,手玩出来了小数据的解就相当于能A掉这个题,真是太神啦。。。
代码
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #include <vector> #define MAXN 110 #define mp(a,b) make_pair(a,b) using namespace std; vector<pair<int,int> >sol[MAXN]; //sol[i]=i对箱子时的解法 int main() { //n=3 sol[3].push_back(mp(2,-1)); sol[3].push_back(mp(5,2)); sol[3].push_back(mp(3,-3)); //n=4 sol[4].push_back(mp(6,-1)); sol[4].push_back(mp(3,6)); sol[4].push_back(mp(0,3)); sol[4].push_back(mp(7,0)); //n=5 sol[5].push_back(mp(8,-1)); sol[5].push_back(mp(3,8)); sol[5].push_back(mp(6,3)); sol[5].push_back(mp(0,6)); sol[5].push_back(mp(9,0)); //n=6 sol[6].push_back(mp(10,-1)); sol[6].push_back(mp(7,10)); sol[6].push_back(mp(2,7)); sol[6].push_back(mp(6,2)); sol[6].push_back(mp(0,6)); sol[6].push_back(mp(11,0)); //n=7 sol[7].push_back(mp(12,-1)); sol[7].push_back(mp(5,12)); sol[7].push_back(mp(8,5)); sol[7].push_back(mp(3,8)); sol[7].push_back(mp(9,3)); sol[7].push_back(mp(0,9)); sol[7].push_back(mp(13,0)); for(int i=8;i<=100;i++) //n=i的情况 { sol[i].push_back(mp(2*i-2,-1)); sol[i].push_back(mp(3,2*i-2)); for(int j=0;j<i-4;j++) sol[i].push_back(mp(sol[i-4][j].first+4,sol[i-4][j].second+4)); sol[i].push_back(mp(0,2*i-5)); sol[i].push_back(mp(2*i-1,0)); } int n; while(scanf("%d",&n)!=EOF) { for(int i=0;i<n;i++) printf("%d to %d\n",sol [i].first,sol [i].second); } return 0; }
C.C。r。a。n。e B。a。l。a。n。c。i。n。g(L。A。 6。7。7。2)
题目链接
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=51896题目大意
一个多边形放在水平面上,要在编号为1的端点放上一个大小忽略不计的重物,问放的重物的重量在什么区间内,才能使这个多边形不会往左或往右翻## 思路 ##神神的计算几何+物理力学题我们首先求出这个多边形的重心,那么整个问题可以简化为两个质点,一个代表原来的多边形,另一个代表重物,如下图(X对应多边形代表的质点,红色点代表重物)接上图。考虑往左边翻的情况,求重物质量最大值。则在往左边翻的过程中,两个质点以y=0的最右边的点(简称右支点)为支点,根据杠杆原理,G2L2≤G1L1G_2L_2 \leq G_1L_1,在即将往左边翻的临界状态时,重物合法质量G2G_2取得最大值,不等式两边取等,G2=G1L1L2G_2=\frac {G_1L_1}{L_2}因此我们最终需讨论重物在左支点左边、左支点到右支点之间、右支点右边三种情况,多边形重心在左支点左边、左支点到右支点之间、右支点右边三种情况,组合起来就是3*3=9种情况,分别得到重物合法质量最小值和最大值,具体看代码注释
代码
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #include <cmath> #define MAXN 110000 #define EPS 1e-8 #define INF 1e30 using namespace std; int n; struct Point { int x,y; Point(){} Point(int _x,int _y):x(_x),y(_y){} }points[MAXN]; int cross(Point a,Point b) { return a.x*b.y-a.y*b.x; } int dcmp(double x) { if(fabs(x)<EPS) return 0; if(x>EPS) return 1; return -1; } int main() { while(scanf("%d",&n)!=EOF) { int L=100000,R=-100000; for(int i=1;i<=n;i++) { scanf("%d%d",&points[i].x,&points[i].y); if(points[i].y==0) { L=min(L,points[i].x); R=max(R,points[i].x); } } double centerx=0,centery=0,totS=0; //重心坐标及整个多边形的面积 for(int i=1;i<=n;i++) { int x1=points[i].x,y1=points[i].y; int x2=points[i%n+1].x,y2=points[i%n+1].y; double S=(double)cross(points[i],points[i%n+1])/2; totS+=S; centerx+=S*(x1+x2)/3; centery+=S*(y1+y2)/3; } centerx/=totS,centery/=totS; totS=fabs(totS); double S=totS; double ansL=0,ansR=0; //ans1=往左翻的最小重量,ans2=往右翻的最小重量 bool flag=true; //flag=false表示无论如何都会翻 //往右翻 if(points[1].x>R) //挂重物的点在右支点右边 { if(dcmp(centerx-R)>0) //重心也在右支点右边,肯定翻 flag=false; else { if(dcmp(centerx-L)<0) //重心在左支点左边,则有重量下限,否则会往左边翻 ansL=S*(L-centerx)/(points[1].x-L); else ansL=0; //重心在左支点到右支点之间,而重物在右支点右边,则显然无重量下限 ansR=S*(R-centerx)/(points[1].x-R); } } else if(points[1].x>=L) //挂重物的点在左支点到右支点之间 { if(dcmp(centerx-L)>0&&dcmp(centerx-R)<0) //重心在左支点到右支点之间 { ansL=0; //重量无下限 ansR=INF; //重量无上限 } else if(dcmp(centerx-R)>0) //重心在右支点右边 { if(points[1].x>R) //重物在右支点右边,肯定翻 flag=false; else //重物在左支点到右支点间 { ansL=S*(centerx-R)/(R-points[1].x); ansR=INF; //无重量上限 } } } else //重物在左支点左边 { if(dcmp(centerx-L)<0) //重心在左支点左边 flag=false; else //重心在左支点右边 { if(dcmp(centerx-R)>0) //重心在右支点右边,有重量下限 ansL=S*(centerx-R)/(R-points[1].x); else ansL=0; //重心在左支点到右支点之间,无重量下限 ansR=S*(centerx-L)/(L-points[1].x); } } if(!flag||ansL>ansR) printf("unstable\n"); //无解情况判断 else { printf("%.0lf .. ",floor(ansL)); if(ansR==INF) printf("inf\n"); else printf("%.0lf\n",ceil(ansR)); } } return 0; }
D.G。a。m。e S。t。r。a。t。e。g。y(L。A。 6。7。7。3)
题目链接
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=51897题目大意
alice和bob在玩一个游戏,他们轮流控制一个棋子,这个游戏里每个回合,Alice先在当前所在的点上的一些集合里选一个集合S,在S里选一个点并跳过去,然后Bob在之前选的那个S里也选一个点,再跳过去,对于所有的起点ii和终点jj的组合(i,j)(i,j),问让bob不得不选择跳到终点最少要多少个回合思路
以下参考bin神的优美做法我们可以考虑倒着做:假如终点是j,倒过来推,从1开始枚举回合数cnt,并枚举起点是i(就是判断从点i,经过cnt个回合跳到点j是否可行),而且要时刻维护i到j的路径上经过的点的集合。i要想跳到j,点i上必须得存在一个集合S,使得i到j的路径上经过的点的集合包含S,因为只有这样,当顺过来做游戏时,bob在点ii无论怎么选,都只能往可以到达jj的方向跳。而如果不存在这个S,bob在点i时就肯定不会选择往j那个方向跳。显然最终的答案是小于nn步的,因此每次我们只需要从1到n-1枚举回合数即可代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #include <vector> #define MAXN 30 #define MAXS 4000 using namespace std; vector<int>vec[MAXN]; //保存每个位置可以选择的pos集合 char s[MAXN]; int ans[MAXN][MAXN]; int main() { int n; while(scanf("%d",&n)!=EOF) { for(int i=1;i<=n;i++) vec[i].clear(); //!!!! for(int i=1;i<=n;i++) { int m; scanf("%d",&m); for(int j=1;j<=m;j++) { scanf("%s",s+1); int len=strlen(s+1); int S=0; for(int k=1;k<=len;k++) S|=(1<<(s[k]-'a')); vec[i].push_back(S); } } memset(ans,-1,sizeof(ans)); for(int j=1;j<=n;j++) { int S=1<<(j-1),tmpS; ans[j][j]=0; for(int cnt=1;cnt<n;cnt++) { tmpS=S; for(int i=1;i<=n;i++) if(ans[i][j]==-1) { bool flag=false; for(int k=0;k<vec[i].size();k++) if((S&vec[i][k])==vec[i][k]) { flag=true; break; } if(flag) { tmpS|=(1<<(i-1)); ans[i][j]=cnt; } } S=tmpS; } } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) printf("%d%c",ans[i][j],j==n?'\n':' '); } return 0; }
F.M.e.s.s.e.n.g.e.r(L。A。 6。7。7。5)
题目链接
https://icpc.kattis.com/problems/messenger题目大意
alice和bob各自在两条折线上行进,一个邮递员要从alice那拿一个包裹,并以直线移动到bob处,alice和bob、邮递员的速度均为1单位/s,问邮递员最少要走多少秒才能送完包裹思路
实际上可以二分mid,判断答案是否小于等于mid。让alice先移动mid秒,并检查是否存在某个时刻使得alice和bob之间的距离小于等于mid。那么此题只需要解决一个难点,即检查是否存在某个时刻使得alice和bob之间的距离小于等于mid。我们可以按照时间,分段扫描整个行进过程,每一段相当于是alice和bob所在的路线上的两个线段,就是不断地求两个线段上的最近距离的问题了。我们可以维护pA,pB,代表alice和bob当前所在的段是属于区间[pA-1,pA]和[pB-1,pB]的,并维护lastA,lastB,pointA,pointB,代表当前的两根折线各为lastA->pointA,lastB->pointB,每次直接求出pointA,pointB,而lastA和lastB是由上一段的pointA,pointB得到的。
最后我们只需要注意一个问题:给出两个线段,求两个线段的最近距离
不妨设A线段起点为(xP1,yP1)(x_{P_1},y_{P_1}),方向向量(xv1,yv1)(x_{v_1},y_{v_1}),B线段起点为(xP2,yP2)(x_{P_2},y_{P_2}),方向向量(xv2,yv2)(x_{v_2},y_{v_2}),最近距离构成的线段为(xP1+xv1t,yP1+yv1t),(xP2+xv2t,yP2+yv2t)(x_{P_1}+x_{v_1}t,y_{P_1}+y_{v_1}t),(x_{P_2}+x_{v_2}t,y_{P_2}+y_{v_2}t)。
最近距离就是[(xv2t−xv1t)+(xP2−xP1)]2+[(yv2t−yv1t)+(yP2−yP1)]2−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√\sqrt{[(x_{v_2}t-x_{v_1}t)+(x_{P_2}-x_{P_1})]^2+[(y_{v_2}t-y_{v_1}t)+(y_{P_2}-y_{P_1})]^2}
忽略掉根号,只看与tt有关的项
[(xv2−xv1)2+(yv2−yv1)2]t2+2t[(xv2−xv1)(xP2−xP1)+(yv2−yv1)(yP2−yP1)][(x_{v_2}-x_{v_1})^2+(y_{v_2}-y_{v_1})^2]t^2+2t[(x_{v_2}-x_{v_1})(x_{P_2}-x_{P_1})+(y_{v_2}-y_{v_1})(y_{P_2}-y_{P_1})]
因此该二次函数在t=−[(xv2−xv1)(xP2−xP1)+(yv2−yv1)(yP2−yP1)][(xv2−xv1)2+(yv2−yv1)2]t=-\frac{[(x_{v_2}-x_{v_1})(x_{P_2}-x_{P_1})+(y_{v_2}-y_{v_1})(y_{P_2}-y_{P_1})]}{[(x_{v_2}-x_{v_1})^2+(y_{v_2}-y_{v_1})^2]}时取得最小值。
代码
注意此题卡精度卡EPS!#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #include <cmath> #define MAXN 110000 #define EPS 1e-7 using namespace std; struct Point { double x,y; Point(){} Point(double _x,double _y):x(_x),y(_y){} }pathA[MAXN],pathB[MAXN]; Point operator+(Point a,Point b) { return Point(a.x+b.x,a.y+b.y); } Point operator*(Point a,double b) { return Point(a.x*b,a.y*b); } Point operator-(Point a,Point b) { return Point(a.x-b.x,a.y-b.y); } Point operator/(Point a,double b) { return Point(a.x/b,a.y/b); } int nA,nB; double dist(Point a,Point b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } double preDistA[MAXN],preDistB[MAXN]; int tot=0,totB=0; double mindist(Point stA,Point vecA,Point stB,Point vecB) { double a=(vecA.x-vecB.x)*(vecA.x-vecB.x)+(vecA.y-vecB.y)*(vecA.y-vecB.y); double b=(vecA.x-vecB.x)*(stA.x-stB.x)+(vecA.y-vecB.y)*(stA.y-stB.y); double t=a<EPS?0:-b/a; if(t<0) t=0; if(t>1) t=1; return dist(stA+vecA*t,stB+vecB*t); } bool check(double dis) { double endA=0,endB=dis; //当前在A的折线上和B的折线上已经扫过的区间为[1,endA],[1,endB] int pA=1,pB=lower_bound(preDistB+1,preDistB+nB+1,dis-EPS)-preDistB; //当前在A的路径上以pA下标开始扫,当前在B的路径上以pB下标开始扫 if(pB>nB) return false; Point lastA=pathA[pA],lastB; if(pB==1) lastB=pathB[pB]; else lastB=pathB[pB-1]+((pathB[pB]-pathB[pB-1])/(preDistB[pB]-preDistB[pB-1]))*(endB-preDistB[pB-1]); double minDist=1e20; while(pA<=nA&&pB<=nB) { if(fabs(preDistA[pA]-endA)<EPS) pA++; if(fabs(preDistB[pB]-endB)<EPS) pB++; if(pA>nA||pB>nB) break; double len=min(preDistA[pA]-endA,preDistB[pB]-endB); endA+=len,endB+=len; Point pointA=pathA[pA-1]+((pathA[pA]-pathA[pA-1])/(preDistA[pA]-preDistA[pA-1]))*(endA-preDistA[pA-1]); //在A的路径里,当前段为lastA->pointA Point pointB=pathB[pB-1]+((pathB[pB]-pathB[pB-1])/(preDistB[pB]-preDistB[pB-1]))*(endB-preDistB[pB-1]); minDist=min(mindist(lastA,pointA-lastA,lastB,pointB-lastB),minDist); lastA=pointA,lastB=pointB; } if(minDist-dis<EPS) return true; //!!!! return false; } int main() { scanf("%d",&nA); for(int i=1;i<=nA;i++) { scanf("%lf%lf",&pathA[i].x,&pathA[i].y); if(i>1) preDistA[i]=preDistA[i-1]+dist(pathA[i-1],pathA[i]); } scanf("%d",&nB); for(int i=1;i<=nB;i++) { scanf("%lf%lf",&pathB[i].x,&pathB[i].y); if(i>1) preDistB[i]=preDistB[i-1]+dist(pathB[i-1],pathB[i]); } if(preDistB[nB]<dist(pathA[1],pathB[nB])-EPS) { printf("impossible\n"); return 0; } double lowerBound=0,upperBound=preDistB[nB],ans=-1; while(fabs(upperBound-lowerBound)>EPS) { double mid=(upperBound+lowerBound)/2; if(check(mid)) upperBound=mid; else lowerBound=mid; } //if(ans<0) printf("impossible\n"); printf("%lf\n",(lowerBound+upperBound)/2); return 0; }
I.S.e.n.s.o.r. N。e。t。w。o。r。k(L。A。 6。7。7。3)
题目链接
https://icpc.kattis.com/problems/sensor题目大意
给你平面上的n个点,要从中找个子集,使得子集中的点两两距离不超过dd,n≤100n\leq 100思路
我们把两两距离不超过dd的点都连边,那么下面要做的就是一个裸的最大团问题了。直接上random shuffle乱搞。具体做法是,随机一个加点的顺序序列,并维护当前可以加入到最大团里的点的集合canaddcanadd,然后从左到右扫一遍这个加点顺序序列,若当前的点在canaddcanadd中,则强制在最大团中加入当前的点,并更新canaddcanadd,否则跳过。如此反复得到一个团,并更新答案。这样随机1000次加点顺序序列,就能求出正确答案了
代码
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #include <bitset> #define MAXN 110 using namespace std; struct Point { int x,y; Point(){} }points[MAXN]; int dist(Point a,Point b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } int ranklist[MAXN]; bitset<MAXN>mp[MAXN],inGroup,now,ans,mark; //now=当前的团的二进制数集合,ans=最大团的二进制数集合 int main() { int n,d; scanf("%d%d",&n,&d); for(int i=1;i<=n;i++) scanf("%d%d",&points[i].x,&points[i].y); for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(dist(points[i],points[j])<=d*d) mp[i][j]=mp[j][i]=true; for(int i=1;i<=n;i++) { ranklist[i]=i; mark[i]=true; } for(int T=1;T<=1000;T++) { bitset<MAXN>canadd=mark; //canadd[i]=true表示第i个点可以被加入到团中 now.reset(); for(int i=1;i<=n;i++) if(canadd[ranklist[i]]) { now[ranklist[i]]=true; canadd&=mp[ranklist[i]]; } if(now.count()>ans.count()) ans=now; random_shuffle(ranklist+1,ranklist+n+1); } printf("%d\n",ans.count()); for(int i=1;i<=n;i++) if(ans[i]) printf("%d ",i); printf("\n"); return 0; }
K.S.u。r。v。e。i。l。l。a。n。c。e(L。A。 6。7。8。0)
题目链接
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=66074题目大意
给你一个长度为lenlen的环,以及nn个的区间,要你选择尽量少的区间,使得它们完全覆盖整个环。问最少要多少个区间思路
如果是一条链的话,我们很容易想到O(nlogn)O(nlogn)的贪心。但是这里是环,显然我们首先要断环为链,然而直接套用链上的贪心的话,我们需要枚举区间的起点,那么复杂度变成了O(n2)O(n^2)。在贪心过程中,我们每次是在当前已经覆盖的区间[L,R][L,R]的右端点开始,找一个右端点尽量大的区间[L′,R′][L',R'],使得R′R'尽量大,且L′≤R+1L'\leq R+1。我们不妨记录下每个编号为ii的区间[L,R][L,R]所对应的另一个编号为jj的区间[L′,R′][L',R'],使得L′≤R+1L'\leq R+1,且R′R'最大,建立一个树,在树中标记jj是ii的父亲。然后我们枚举起点区间,并每次在O(logn)O(logn)时间内进行倍增,找出要让覆盖部分的区间长度大于等于lenlen最少要多少个区间。
代码
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 2100000 #define INF 0x3f3f3f3f using namespace std; struct Segment { int L,R; }seg[MAXN]; int len,n; bool cmp(Segment a,Segment b) { return a.R<b.R; } bool cmp2(int a,int b) { return seg[a].R<seg[b].R; } int fa[MAXN][30],depth[MAXN]; void getDepth(int x) { if(!fa[x][0]) depth[x]=1; if(depth[x]) return; getDepth(fa[x][0]); depth[x]=depth[fa[x][0]]+1; } void LCA_prework() { for(int i=1;i<=n;i++) getDepth(i); for(int j=1;j<=19;j++) for(int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } int query(int x,int lim) //从区间x开始往后找到一个区间y,使得y的右端点的长度>=lim { for(int i=19;i>=0;i--) if(fa[x][i]&&seg[fa[x][i]].R<lim) x=fa[x][i]; if(seg[x].R>=lim) return x; return fa[x][0]; } int maxR[MAXN]; //maxR[x]=左端点为x的右端点最远的区间编号 int main() { scanf("%d%d",&len,&n); for(int i=1;i<=n;i++) { int x,y; scanf("%d%d",&x,&y); if(x>y) {seg[i].L=x,seg[i].R=len+y;} else {seg[i].L=x,seg[i].R=y;} } sort(seg+1,seg+n+1,cmp); for(int i=1;i<=n;i++) maxR[seg[i].L]=max(maxR[seg[i].L],i); int tmp=0; for(int i=1,j=1;i<=n;i++) { for(;j<=seg[i].R+1;j++) tmp=max(tmp,maxR[j],cmp2); if(tmp!=i) fa[i][0]=tmp; } LCA_prework(); int ans=0x3f3f3f3f; for(int i=1;i<=n;i++) { int j=query(i,seg[i].L+len-1); //找到区间j,使得j的右端点>=i的左端点+len-1 if(j) ans=min(ans,depth[i]-depth[j]+1); } if(ans==0x3f3f3f3f) printf("impossible\n"); else printf("%d\n",ans); return 0; }