COCICONTEST# 29.11.2014# 题解+总结

NOIP临近,刷刷题。
这场比赛下来明显觉得自己代码能力不够,比如第五题stogovi很明显的LCA,也想到是用LCA,然而并木有写出来,对模板的应用不够熟练。在思维方面不够严谨,比如第四题coci,没有意识到题目给的选手分数范围的意义,也可能是因为读题不够仔细吧。

在时间分配上还存在严重问题,前面的水题不能保证正确性,想错或者在某个地方手抽打错,在调试上花了很多时间以至于想后面的题时有一点慌。这也可能是因为码代码的速度不够快吧。

第一题: strojopis
打表模拟,水题一道。

#include <iostream>
#include <cstdio>
using namespace std;

int num[105] ;
char a ;
int cnt[10] ;

int main()
{
    num[49]=num[81]=num[65]=num[90]=1 ;
    num[50]=num[87]=num[83]=num[88]=2 ;
    num[51]=num[69]=num[68]=num[67]=3 ;
    num[52]=num[82]=num[70]=num[86]=num[53]=num[84]=num[71]=num[66]=4 ;
    num[54]=num[89]=num[72]=num[55]=num[85]=num[74]=num[77]=num[78]=5 ;
    num[56]=num[73]=num[75]=num[44]=6 ;
    num[57]=num[79]=num[76]=num[46]=7 ;
    num[48]=num[80]=num[59]=num[47]=num[45]=num[91]=num[39]=num[61]=num[93]=8 ;
    while(~scanf("%c",&a))
        ++cnt[num[a]] ;
    for(int i=1;i<=8;++i)
        printf("%d\n",cnt[i]);
    return 0;
}

第二题: dom
不说了,水题一道。
但蒟蒻在写代码的时候习惯不好,明明可以用while循环写的,却手抽用递归,爆栈不解释。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#define MAXN 100010
using namespace std;

int n ,m ,p ,a ,b ,next[MAXN] ,ans ;
bool vis[MAXN] ;

int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&a,&b);
        if(!next[b])
            next[b]=a;
    }
    vis[p]=1;
    while(next[p])
    {
        ++ans;
        p=next[p];
        if(vis[p])
        {
            ans=-1;
            break;
        }
        vis[p]=1;
    }
    printf("%d\n",ans);
    return 0;
}

第三题: silueta
模拟水题一道,但蒟蒻竟然分都不分 %>_<%
枚举每一列,寻找到最高位置。由于该建筑物的边框只会是连续的一段,所有最高位置就是边框的终点。比较它和前一列的高度,周长肯定要加上它俩的高度差以及这一列的宽度(就是 1 )。如果出现了这一列比前一列低,就应该去填前一列。所以在代码的 31 行进行分类。

#include <iostream>
#include <cstdio>
#include <cstring>
#define abs(a) ((a)>0?(a):-(a))
#define MAXN 1000
#define MAXM 10005
using namespace std;

int ans ,n ,l[MAXM] ,r[MAXM] ,h[MAXM] ,maxh[MAXM] ,tmp1 ,tmp2 ,tmp3 ;
char map[MAXN+5][MAXN+5] ;

int main()
{
    scanf("%d",&n);
    memset(map,'.',sizeof map);
    tmp1=MAXM ;
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&l[i],&r[i],&h[i]);
        --r[i];
        tmp1=min(tmp1,l[i]) ,tmp2=max(tmp2,r[i]) ;
    }
    for(int i=tmp1;i<=tmp2+1;++i)
    {
        for(int j=1;j<=n;++j)
            if(l[j]<=i&&i<=r[j])
                maxh[i]=max(maxh[i],h[j]);
        ans+=abs(maxh[i]-maxh[i-1]);
        if(maxh[i]>0)++ans;
        int end=max(maxh[i],maxh[i-1]);
        if(maxh[i]<maxh[i-1])
            for(int j=min(maxh[i],maxh[i-1]);j<=end;++j)
                map[j][i-1]='#';
        else
            for(int j=min(maxh[i],maxh[i-1]);j<=end;++j)
                map[j][i]='#';
        map[maxh[i]][i]='#';
        tmp3=max(tmp3,maxh[i]);
    }
    printf("%d\n",ans);
    for(int i=tmp3;i>0;--i)
    {
        for(int j=tmp1;j<=tmp2;++j)
            printf("%c",map[i][j]);
        puts("");
    }
    for(int j=tmp1;j<=tmp2;++j)
        printf("*");
    puts("");
    return 0;
}

第四题: honi
题目大意:给你 n 个选手的前两场比赛的分数,计算出他的总排名的范围。若选手A的前两场的分数都比选手B高,那么A第三场的分数也会比B高。分数的范围为 [0,650]

蒟蒻一开始以为分数的范围并没有用,就用树状数组乱YY了一种方法wa了 4 个点。事实上分数的范围是有用的,见下面的例子。
2
650 1
0 1
若没有分数范围,那么两个人的排名都为 [1,2] ,但答案为 [1,1] [1,2] ,因为即使第一个人第三场得 0 分,第二个人得 650 分,第一个人也还是第一名。

正解:令 s[i][j] 表示第一场得分 i 第二场得分 j 总人数。令 num[i][j] 表示第一场得分 x<=i 并且第二场得分 y<=j 的总人数。那么他的最高排名就是比他得分都高的总人数,最低排名就是 ans=n 比他得分低的总人数。在算最低排名的时候要特判得分为最高分的情况(原因见上面的例子)。

#include <iostream>
#include <cstdio>
#define MAXN 500005
#define MAXM 650
using namespace std;

int n ,a[MAXN] ,b[MAXN] ,ans ;
int num[MAXM+5][MAXM+5] ,s[MAXM+5][MAXM+5] ;

int solve(int x1,int y1,int x2,int y2)
{
    if(x2<0||y2<0)
        return 0;
    int ans=num[x2][y2] ;
    if(y1>0)
        ans-=num[x2][y1-1];
    if(x1>0)
        ans-=num[x1-1][y2] ;
    if(x1>0&&y1>0)
        ans+=num[x1-1][y1-1] ;
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&a[i],&b[i]);
        ++s[a[i]][b[i]] ;
    }

    for(int i=0;i<=MAXM;++i)
        for(int j=0;j<=MAXM;++j)
        {
            num[i][j]=s[i][j];
            if(i>0)
                num[i][j]+=num[i-1][j];
            if(j>0)
                num[i][j]+=num[i][j-1];
            if(i>0&&j>0)
                num[i][j]-=num[i-1][j-1] ;
        }
    for(int i=1;i<=n;++i)
    {
        printf("%d ",solve(a[i]+1,b[i]+1,MAXM,MAXM)+1);
        ans=n-solve(0,0,a[i]-1,b[i]-1) ;
        if(a[i]==MAXM)ans-=s[0][b[i]];
        if(b[i]==MAXM)ans-=s[a[i]][0];
        printf("%d\n",ans);
    }
    return 0;
}

第五题: stogovi
很明显的LCA。
如果是加入元素 i 入栈,那么 i 号节点的父亲就是他自己。
而对于操作b,即弹出栈顶元素, i 的父亲就变为 v 的父亲,同时 i 的祖先也要跟着改变。(这个转化当时木有想到就木有写出来%>_<%)
而对于操作c,就找它俩的最近公共祖先,输出祖先的长度即可。

#include <iostream>
#include <cstdio>
#define MAXN 300005
#define MAXK 20
using namespace std;

int n ,fa[MAXN] ,f[MAXN][MAXK] ,v ,w ,size[MAXN] ;
char word[5] ;

void adjust(int &u,int d)
{
    for(int j=MAXK-1;j>=0;--j)
        if(size[f[u][j]]>=d)
            u=f[u][j] ;
}

int lca(int u,int v)
{
    if(size[u]<size[v])
        swap(u,v);
    if(size[u]>size[v])
        adjust(u,size[v]);
    if(u==v)
        return u;
    for(int j=MAXK-1;j>=0;--j)
        if(f[u][j]!=f[v][j])
            u=f[u][j] ,v=f[v][j] ;
    return f[u][0] ;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%s%d",word,&v);
        v=fa[v] ;
        fa[i]=v ;
        if(word[0]=='a')
        {
            fa[i]=i ;
            size[i]=size[v]+1 ;
            f[i][0]=v ;
            for(int j=1;j<MAXK;++j)
                f[i][j]=f[f[i][j-1]][j-1] ;
        }
        else if(word[0]=='b')
        {
            printf("%d\n",fa[i]);
            fa[i]=f[fa[i]][0] ;
        }
        else
        {
            scanf("%d",&w);
            w=fa[w] ;
            printf("%d\n",size[lca(v,w)]);
        }
    }
    return 0;
}

第六题: kamioni
当时在考场上就想过将所有询问保存起来,然后所有卡车一起跑,处理卡车掉头的地方,每一次都对询问处理一次。然而蒟蒻却更新了所有的卡车,就发现还没爆搜跑得快,于是果断写暴力= =

正解:将每一辆卡车的掉头地点、时间以及方向存起来,按时间排序。对于一个询问,只需要处理一个卡车就行了。所以将这个询问放在掉头数小的那辆车上。更新跟它有询问的车就行了。

接下来分析时间复杂度。
若一辆卡车的掉头数超过了 sqrt(k) ,那么它的询问肯定不会超过 sqrt(k) 。假设它的询问超过了 sqrt(k) ,那么那些对应的卡车的掉头次数超过了 sqrt(k) ,总和就已经超过了 k ,矛盾。
若一辆卡车的掉头数少于了 sqrt(k) ,虽然它的询问可能会超过 sqrt(k) ,但是掉头数少于 sqrt(k)
这样均摊下来,时间复杂度为 O(ksqrt(k))

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#define abs(a) ((a)>0?(a):-(a))
#define MAXN 100005
#define MAXK 300005
#define pii pair<int,int>
#define LL long long int
using namespace std;
map<pii,vector<int> >sameask;

int n ,m ,a ,b ,pos1 ;
int num[MAXN] ,dir[MAXN] ,ans[MAXN] ;
LL ti ;

struct Car
{
    int id ,pos ,dir ;
    LL t;
    bool operator < (const Car &a)const
    {
        if(t!=a.t)return t<a.t;
        return dir>a.dir;
    }
}car[MAXK] ,truck[MAXN] ;

struct node
{
    int id ,v ;
    node *next ;
}edge[MAXN] ,*adj[MAXN] ,*code=edge ;

int check_left(const Car &a,const Car &b,LL t,int dir)
{
    if(b.dir==0&&t>=b.t)return 0;
    int apos=a.pos ,bpos=b.pos ;
    if(b.dir==0)
        apos+=(a.t-b.t)*(-dir);
    else bpos+=(a.t-b.t)*b.dir;
    if(apos<bpos)
        return 1;
    return -1;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&num[i],&a);
        ti=0;
        for(int j=1;j<num[i];++j)
        {
            scanf("%d",&b);
            Car e={i,a,a<b?1:-1,ti};
            if(j==1)
                truck[i]=e;
            else car[pos1++]=e;
            ti+=abs(a-b) ,a=b ;
        }
        Car e={i,a,0,ti};
        car[pos1++]=e;
    }
    for(int i=0;i<m;++i)
    {
        scanf("%d%d",&a,&b);
        if(a>b)swap(a,b);
        sameask[pii(a,b)].push_back(i);
        if((int)sameask[pii(a,b)].size()>1)continue;
        if(num[a]>num[b])swap(a,b);
        code->v=b ,code->id=i ,code->next=adj[a] ;
        adj[a]=code ;
        ++code ;
        dir[i]=truck[a].pos<truck[b].pos?1:-1;
    }
    sort(car,car+pos1);
    int id ,temp ,dir1 ;
    for(int i=0;i<pos1;++i)
    {
        id=car[i].id ;
        ti=truck[id].t ,dir1=truck[id].dir ;
        truck[id]=car[i];
        for(node *p=adj[id];p!=NULL;p=p->next)
        {
            temp=check_left(truck[id],truck[p->v],ti,dir1);
            ans[p->id]+=(dir[p->id]*temp==-1);
            dir[p->id]=temp;
        }
    }
    int res ;
    for(map<pii,vector<int> >::iterator p=sameask.begin();p!=sameask.end();p++)
    {
        res=ans[p->second[0]];
        for(int i=1;i<p->second.size();++i)
            ans[p->second[i]]=res;
    }
    for(int i=0;i<m;++i)
        printf("%d\n",ans[i]);
    return 0;
}

你可能感兴趣的:(coci)