文章标题

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[n][i].first,sol[n][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的最右边的点(简称右支点)为支点,根据杠杆原理, G2L2G1L1 ,在即将往左边翻的临界状态时,重物合法质量 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;
}

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里也选一个点,再跳过去,对于所有的起点 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;
}

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),方向向量 (xv1,yv1) ,B线段起点为 (xP2,yP2) ,方向向量 (xv2,yv2) ,最近距离构成的线段为 (xP1+xv1t,yP1+yv1t),(xP2+xv2t,yP2+yv2t)

最近距离就是 [(xv2txv1t)+(xP2xP1)]2+[(yv2tyv1t)+(yP2yP1)]2
忽略掉根号,只看与 t 有关的项

[(xv2xv1)2+(yv2yv1)2]t2+2t[(xv2xv1)(xP2xP1)+(yv2yv1)(yP2yP1)]

因此该二次函数在 t=[(xv2xv1)(xP2xP1)+(yv2yv1)(yP2yP1)][(xv2xv1)2+(yv2yv1)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个点,要从中找个子集,使得子集中的点两两距离不超过 d n100

思路

我们把两两距离不超过 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;
}

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

题目大意

给你一个长度为 len 的环,以及 n 个的区间,要你选择尽量少的区间,使得它们完全覆盖整个环。问最少要多少个区间

思路

如果是一条链的话,我们很容易想到O(nlogn)的贪心。但是这里是环,显然我们首先要断环为链,然而直接套用链上的贪心的话,我们需要枚举区间的起点,那么复杂度变成了 O(n2)

在贪心过程中,我们每次是在当前已经覆盖的区间 [L,R] 的右端点开始,找一个右端点尽量大的区间 [L,R] ,使得 R 尽量大,且 LR+1 。我们不妨记录下每个编号为 i 的区间[L,R]所对应的另一个编号为 j 的区间[L,R],使得 LR+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值