注:此题题解里所有的图片来自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[n][i].first,sol[n][i].second);
}
return 0;
}
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=51896
一个多边形放在水平面上,要在编号为1的端点放上一个大小忽略不计的重物,问放的重物的重量在什么区间内,才能使这个多边形不会往左或往右翻## 思路 ##神神的计算几何+物理力学题我们首先求出这个多边形的重心,那么整个问题可以简化为两个质点,一个代表原来的多边形,另一个代表重物,如下图(X对应多边形代表的质点,红色点代表重物)
接上图。考虑往左边翻的情况,求重物质量最大值。则在往左边翻的过程中,两个质点以y=0的最右边的点(简称右支点)为支点,根据杠杆原理, G2L2≤G1L1 ,在即将往左边翻的临界状态时,重物合法质量 G2 取得最大值,不等式两边取等, G2=G1L1L2 因此我们最终需讨论重物在左支点左边、左支点到右支点之间、右支点右边三种情况,多边形重心在左支点左边、左支点到右支点之间、右支点右边三种情况,组合起来就是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;
}
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=51897
alice和bob在玩一个游戏,他们轮流控制一个棋子,这个游戏里每个回合,Alice先在当前所在的点上的一些集合里选一个集合S,在S里选一个点并跳过去,然后Bob在之前选的那个S里也选一个点,再跳过去,对于所有的起点 i 和终点 j 的组合 (i,j) ,问让bob不得不选择跳到终点最少要多少个回合
以下参考bin神的优美做法我们可以考虑倒着做:假如终点是j,倒过来推,从1开始枚举回合数cnt,并枚举起点是i(就是判断从点i,经过cnt个回合跳到点j是否可行),而且要时刻维护i到j的路径上经过的点的集合。i要想跳到j,点i上必须得存在一个集合S,使得i到j的路径上经过的点的集合包含S,因为只有这样,当顺过来做游戏时,bob在点 i 无论怎么选,都只能往可以到达 j 的方向跳。而如果不存在这个S,bob在点i时就肯定不会选择往j那个方向跳。显然最终的答案是小于 n 步的,因此每次我们只需要从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;
}
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) ,方向向量 (xv1,yv1) ,B线段起点为 (xP2,yP2) ,方向向量 (xv2,yv2) ,最近距离构成的线段为 (xP1+xv1t,yP1+yv1t),(xP2+xv2t,yP2+yv2t) 。
最近距离就是 [(xv2t−xv1t)+(xP2−xP1)]2+[(yv2t−yv1t)+(yP2−yP1)]2−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√
忽略掉根号,只看与 t 有关的项
因此该二次函数在 t=−[(xv2−xv1)(xP2−xP1)+(yv2−yv1)(yP2−yP1)][(xv2−xv1)2+(yv2−yv1)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;
}
https://icpc.kattis.com/problems/sensor
给你平面上的n个点,要从中找个子集,使得子集中的点两两距离不超过 d , n≤100
我们把两两距离不超过 d 的点都连边,那么下面要做的就是一个裸的最大团问题了。直接上random shuffle乱搞。
具体做法是,随机一个加点的顺序序列,并维护当前可以加入到最大团里的点的集合 canadd ,然后从左到右扫一遍这个加点顺序序列,若当前的点在 canadd 中,则强制在最大团中加入当前的点,并更新 canadd ,否则跳过。如此反复得到一个团,并更新答案。这样随机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;
}
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=66074
给你一个长度为 len 的环,以及 n 个的区间,要你选择尽量少的区间,使得它们完全覆盖整个环。问最少要多少个区间
如果是一条链的话,我们很容易想到 O(nlogn) 的贪心。但是这里是环,显然我们首先要断环为链,然而直接套用链上的贪心的话,我们需要枚举区间的起点,那么复杂度变成了 O(n2) 。
在贪心过程中,我们每次是在当前已经覆盖的区间 [L,R] 的右端点开始,找一个右端点尽量大的区间 [L′,R′] ,使得 R′ 尽量大,且 L′≤R+1 。我们不妨记录下每个编号为 i 的区间 [L,R] 所对应的另一个编号为 j 的区间 [L′,R′] ,使得 L′≤R+1 ,且 R′ 最大,建立一个树,在树中标记 j 是 i 的父亲。然后我们枚举起点区间,并每次在 O(logn) 时间内进行倍增,找出要让覆盖部分的区间长度大于等于 len 最少要多少个区间。
#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;
}