BestCoder Round #2 解题报告

本文总结了作者在暑期面对编程效率下降的问题,并分享了解决方法。同时,详细解析了四道编程题目的解题思路和代码实现,涉及区间更新、模拟算法、平面图搜索及字符串处理等算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近感觉暑假在家好颓,效率直线下降,码代码速度慢,想题想不出。这次R2的题目也是过了很久才做完……但愿8月份到了学校情况会好些吧。


T1:TIANKENG’s restaurant

题目传送门:http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=526&pid=1001

题目大意:给出n(n<=10000)个时间段(形式为hh:mm~hh:mm)和数字x,表示这个时间段会有x个客人来饭店就餐。每个客人需要一张椅子,问最少需要多少张椅子(一个客人离开后椅子可以立马给另一个客人坐)。不超过100组数据,保证时间段不会跨过凌晨0点。

题目分析:这题本质上就是区间加x,最后每一个数取个max。我们在开始时刻+1,在结束时刻后一分钟-1,然后从左往右扫一遍就可以求出答案。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1450;

int temp[maxn];
int T,n;

int Read()
{
    int x=0;
    char c=getchar();
    while ( c<'0' || '9'<c ) c=getchar();
    while ( '0'<=c && c<='9' ) x=(x<<3)+(x<<1)+(c-'0'),c=getchar();
    return x;
}

int Get(int x,int y)
{
    return 60*x+y;
}

int main()
{
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);

    scanf("%d",&T);
    while (T--)
    {
        memset(temp,0,sizeof(temp));
        scanf("%d",&n);
        for (int i=1; i<=n; i++)
        {
            int x;
            scanf("%d",&x);
            int h=Read(),m=Read();
            temp[ Get(h,m) ]+=x;
            h=Read(),m=Read();
            temp[ Get(h,m) ]-=x;
        }
        int ans=0,now=0;
        for (int i=0; i<maxn; i++)
            now+=temp[i],ans=max(now,ans);
        printf("%d\n",ans);
    }

    return 0;
}

T2:TIANKENG’s rice shop

题目传送门:http://bestcoder.hdu.edu.cn/contests/contest_show.php?cid=526

题目大意:餐馆提供n(n<=1000)种食物。现在有m(m<=1000)个客人在hh:mm时刻来餐馆排队,每个人会点某一种食物num(num<=10)份。厨师会按照顺序给他们做饭,他会用t(t<=10)分钟做好k(k<=5)份相同类型的食物,如果供应完当前客人之后还有多余的几份,他就会将剩下这些分给已经来排队的要点该类型食物的人(靠前优先),如果还有多余的则扔掉。假设每个客人在拿到num份食物后就会离开,求每个客人的离开时刻。客人抵达的时间不会跨过凌晨0点,不超过100组数据。

题目分析:这题就是个恶心的硬模拟。我们直接开一个n*m的数组维护一下每一种食物有哪些客人点即可,时间O(Tm),其中T是数据组数。这里还有一个很坑的地方:虽然客人抵达的时间顶多是当天23:59,但是他离开的时间可能是好几天后。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1010;

int id[maxn][maxn];
int head[maxn];
int tail[maxn];

int Type[maxn];
int Time[maxn];
int num[maxn];

int ans[maxn];
int T,n,t,k,m;

int Read()
{
    int x=0;
    char c=getchar();
    while ( c<'0' || '9'<c ) c=getchar();
    while ( '0'<=c && c<='9' ) x=(x<<3)+(x<<1)+(c-'0'),c=getchar();
    return x;
}

int Get(int x,int y)
{
    return 60*x+y;
}

void Print(int x)
{
    int hh=x/60;
    hh%=24;
    int mm=x%60;
    if (hh<10) printf("0");
    printf("%d:",hh);
    if (mm<10) printf("0");
    printf("%d\n",mm);
}

int main()
{
    freopen("2.in","r",stdin);
    freopen("2.out","w",stdout);

    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d%d%d",&n,&t,&k,&m);
        for (int i=1; i<=n; i++) head[i]=0,tail[i]=0;
        for (int i=1; i<=m; i++)
        {
            int hh=Read(),mm=Read(),y;
            scanf("%d%d",&Type[i],&y);
            Time[i]=Get(hh,mm);
            num[i]=y;
            id[ Type[i] ][ ++tail[ Type[i] ] ]=i;
        }

        int nt=0;
        for (int i=1; i<=m; i++) if (num[i])
        {
            nt=max(nt,Time[i]);
            int x=Type[i];
            while (num[i]>k) num[i]-=k,nt+=t;
            nt+=t;

            int Left=k;
            while ( head[x]<tail[x] && Time[ id[x][ head[x]+1 ] ]<=nt-t )
            {
                head[x]++;
                int y=id[x][ head[x] ];
                if (num[y]<=Left)
                {
                    Left-=num[y];
                    num[y]=0;
                    ans[y]=nt;
                }
                else
                {
                    num[y]-=Left;
                    head[x]--;
                    break;
                }
            }
        }

        for (int i=1; i<=m; i++) Print(ans[i]);
        if (T) printf("\n");
    }

    return 0;
}

T3:TIANKENG’s travel

题目传送门:http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=526&pid=1003

题目大意:在一个平面图上给出起点,终点,以及n(n<=1000)个加油站。从一个点可以行驶到另一个和它欧几里得距离小于等于L的点,而且必须直线行驶。就是说如果两点的连线之间还有加油站,那么肯定行驶时必定会经过这个加油站。现在要求最小化从起点到终点所经过的加油站个数,不超过30组数据。

题目分析:这题我一开始没看见必须直线行驶这个条件,以为是 n2 暴力连边然后BFS,但我又觉得30组数据可能会超 (3107) 。于是我不停地想 nlog(n) 的做法,结果根本不会。后来我看了题解才发现要用 n2log(n) 判断三点共线……
由于两个点中间有加油站时不能直接连边,所以我们先将点按x坐标从小到大排序,相同则按y从小到大。这样点i连向点j的边 (i<j) 的极角就是 (π2,π2] ,然后我们对一个点i的所有射出的边按极角大小排序,然后相同角度的只连该射线上的第一个点即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1010;
typedef long long LL;

struct edge
{
    int obj;
    edge *Next;
} e[maxn*maxn];
edge *head[maxn];
int cur;

int level[maxn];
int que[maxn];
int he,ta;

struct data
{
    int dx,dy,id;
} line[maxn][maxn];

struct point
{
    int X,Y,T;
} gas[maxn];
int G,n,l;

bool Comp1(point x,point y)
{
    return x.X<y.X || ( x.X==y.X && x.Y<y.Y );
}

bool Comp2(data x,data y)
{
    LL temp1=x.dy;
    temp1*=y.dx;
    LL temp2=y.dy;
    temp2*=x.dx;
    bool flag=( x.dx<y.dx || ( x.dx==y.dx && x.dy<y.dy ) );
    return temp1<temp2 || ( temp1==temp2 && flag );
}

bool Judge(int x,int y)
{
    LL temp1=gas[x].X-gas[y].X;
    temp1*=temp1;
    LL temp2=gas[x].Y-gas[y].Y;
    temp2*=temp2;
    temp1+=temp2;
    temp2=l;
    temp2*=temp2;
    return temp1<=temp2; 
}

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

int Bfs()
{
    int s,t;
    for (int i=0; i<=n; i++)
    {
        level[i]=0;
        if (!gas[i].T) s=i;
        if (gas[i].T==n) t=i;
    }
    he=0,ta=1,que[1]=s;
    while (he<ta)
    {
        int node=que[++he];
        for (edge *p=head[node]; p; p=p->Next)
        {
            int son=p->obj;
            if ( son && !level[son] )
            {
                level[son]=level[node]+1;
                que[++ta]=son;
            }
        }
    }
    return level[t];
}

int main()
{
    freopen("3.in","r",stdin);
    freopen("3.out","w",stdout);

    scanf("%d",&G);
    while (G--)
    {
        scanf("%d%d",&n,&l);
        n++;
        scanf("%d%d",&gas[0].X,&gas[0].Y);
        scanf("%d%d",&gas[n].X,&gas[n].Y);
        for (int i=1; i<n; i++) scanf("%d%d",&gas[i].X,&gas[i].Y);
        for (int i=0; i<=n; i++) gas[i].T=i; 
        sort(gas,gas+n+1,Comp1);

        cur=-1;
        for (int i=0; i<=n; i++) head[i]=NULL;
        for (int i=0; i<n; i++)
        {
            for (int j=i+1; j<=n; j++)
            {
                line[i][j-i].id=j;
                line[i][j-i].dx=gas[j].X-gas[i].X;
                line[i][j-i].dy=gas[j].Y-gas[i].Y;
            }
            sort(line[i]+1,line[i]+n-i+1,Comp2);

            if ( Judge(i,line[i][1].id) )
                Add(i,line[i][1].id),Add(line[i][1].id,i);
            for (int j=2; j<=n-i; j++)
            {
                LL temp1=line[i][j].dy;
                temp1*=line[i][j-1].dx;
                LL temp2=line[i][j-1].dy;
                temp2*=line[i][j].dx;
                if ( temp1!=temp2 && Judge(i,line[i][j].id) )
                    Add(i,line[i][j].id),Add(line[i][j].id,i);
            }
        }

        int ans=Bfs();
        if (ans) printf("%d\n",ans-1);
        else printf("impossible\n");
    }

    return 0;
}

T4:TIANKENG’s restaurant(Ⅱ)

题目传送门:http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=526&pid=1004

题目大意:给出一个只包含A~H,长度不超过 106 的字符串,输出一个最短的只包含A~H的答案串,使得答案串不是给定串的子串。如果有多组解,则输出字典序最小的那个。

题目分析:这题空间只有64M我还能说什么……当初一看这题很高兴因为这就是个SAM的裸题呀。我们把后缀自动机构出来然后就是要从根节点最快地转移到NULL并且保证字典序最小,结果空间炸得不要不要的。我伤心无奈之下忽然想起陈老师PPT里说SAM的边数不会超过状态数+字符串长度,于是我就把son[8]的指针数组改成了3*len的邻接链表(本来想写treap的,但考虑到空间开销……),时间多了个常数8而已。然而还是炸空间,后来看题解知道这就是个Hash?!
我们首先想答案会有多长。如果答案长度是7,那么长度为7的串就有 87=2097152 个,而原串中长度为7的子串顶多只有 10000007+1=999994 个,于是答案长度肯定不超过7。我们不妨枚举长度ans,将字符串长度为ans的子串的hash值用一个下标数组记录,最后遍历一边下标数组即可。时间复杂度 O(n)
时候我对拍了一下SAM+邻接链表的程序和Hash的程序,发现没什么问题,只不过前者的常数和空间都比较大而已。极限数据Hash要3s,而SAM+邻接链表要30s……

CODE(Hash):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000010;
const int maxl=8;

bool vis[maxn];
int val[maxl];

int w[maxl];
int cur=0;

char s[maxn];
int t,slen;

void Print(int x,int len)
{
    for (int i=1; i<=len; i++) w[++cur]=x&7,x>>=3;
    while (cur) printf("%c",'A'+w[cur]),cur--;
    printf("\n");
}

int main()
{
    freopen("4.in","r",stdin);
    freopen("4.out","w",stdout);

    val[0]=1;
    for (int i=1; i<maxl; i++) val[i]=val[i-1]<<3;

    scanf("%d",&t);
    while (t--)
    {
        scanf("%s",&s);
        slen=strlen(s);

        for (int ans=1; ans<=maxl; ans++)
        {
            memset(vis,false,sizeof(vis));
            int Hash=0;
            for (int i=1; i<=ans; i++) Hash=(Hash<<3)|(s[i-1]-'A');
            for (int i=1; i<=slen-ans+1; i++)
            {
                vis[Hash]=true;
                Hash-=((s[i-1]-'A')*val[ans-1]);
                Hash=(Hash<<3)|(s[i+ans-1]-'A'); 
            }

            bool sol=false;
            for (int i=0; i<val[ans]; i++)
                if (!vis[i])
                {
                    Print(i,ans);
                    sol=true;
                    break;
                }
            if (sol) break;
        }
    }

    return 0;
}

CODE(SAM+邻接链表):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000010;
const int maxc=8;
struct State;

struct edge
{
    int son; 
    State *obj;
    edge *Next;
} e[maxn*3];
int num;

struct State
{
    int val,id,len;
    edge *head;
    State *parent;
} SAM[maxn<<1];
State *Root,*last;
int cur;

int cnt[maxn];
State *Rank[maxn<<1];

char s[maxn];
int T,slen;

State *New_state(int v)
{
    cur++;
    SAM[cur].val=v;
    SAM[cur].head=NULL;
    SAM[cur].parent=NULL;
    return SAM+cur; 
}

State *Get(State *P,int x)
{
    for (edge *p=P->head; p; p=p->Next)
        if (p->son==x) return p->obj;
    return NULL;
}

void Add(State *P,int x,State *Q)
{
    if (!Q) return;
    num++;
    e[num].son=x;
    e[num].obj=Q;
    e[num].Next=P->head;
    P->head=e+num;
}

void Build(int x)
{
    int to=s[x]-'A';
    x++;
    State *P=last,*NP=New_state(x);
    last=NP;
    while ( P && !Get(P,to) ) Add(P,to,NP),P=P->parent;
    if (!P)
    {
        NP->parent=Root;
        return;
    }
    State *Q=Get(P,to);
    if (P->val+1==Q->val) NP->parent=Q;
    else
    {
        State *NQ=New_state(P->val+1);
        for (int i=0; i<maxc; i++) Add(NQ,i, Get(Q,i) );
        NQ->parent=Q->parent;
        NP->parent=Q->parent=NQ;
        while (P)
        {
            State *tag=Get(P,to);
            if (tag!=Q) break;
            for (edge *p=P->head; p; p=p->Next)
                if (p->son==to)
                {
                    p->obj=NQ;
                    break;
                }
            P=P->parent;
        }
    }
}

int main()
{
    freopen("4.in","r",stdin);
    freopen("4.out","w",stdout);

    scanf("%d",&T);
    while (T--)
    {
        scanf("%s",&s);
        slen=strlen(s);

        num=cur=-1;
        last=Root=New_state(0);
        for (int i=0; i<slen; i++) Build(i);

        for (int i=0; i<=slen; i++) cnt[i]=0;
        for (int i=0; i<=cur; i++) cnt[ SAM[i].val ]++;
        for (int i=1; i<=slen; i++) cnt[i]+=cnt[i-1];
        for (int i=0; i<=cur; i++) Rank[ --cnt[ SAM[i].val ] ]=SAM+i;

        for (int i=cur; i>=0; i--)
        {
            Rank[i]->id=0;
            State *tag=Get(Rank[i],0);
            Rank[i]->len=(tag? tag->len:0)+1;
            for (int j=1; j<maxc; j++)
            {
                tag=Get(Rank[i],j);
                int temp=(tag? tag->len:0)+1;
                if (temp<Rank[i]->len) Rank[i]->len=temp,Rank[i]->id=j;
            }
        }

        State *P=Root;
        while (P) printf("%c",'A'+P->id),P=Get(P,P->id);
        printf("\n");
    }

    //printf("%d\n",sizeof(e)/1024);
    //printf("%d\n",sizeof(SAM)/1024);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值