2017.5.17 COCI2014/2015 Contest#5

B 贪吃蛇简化版

【分析】我感觉这题应该有一个比较好的贪心策略,就是“S”形走。我感觉这样挺靠谱的,然后也就这么敲了。当然这个是比较感性的,比较靠直觉,但是还是敲了。。。

【代码】

#include 
using namespace std;
#define oo 1000000007
#define M 1005
int n,m,lim,ans=-1,now=1;
char mp[M][M];
int to[M][M];
int main(){
    scanf("%d %d",&n,&m);
    for(int i=n;i>=1;i--){
        scanf("%s",mp[i]+1);
        to[0][i]=oo;
        for(int j=1;j<=m;j++){
            if(mp[i][j]!='J')continue;
            if(to[0][i]==oo)to[0][i]=j;
            to[1][i]=max(to[1][i],j);
            lim=max(lim,i);
        }
    }
    for(int i=1;i<=lim;i++){
        if(i==lim){
            if(i%2==1)ans+=to[1][i]-now;
            else ans+=now-to[0][i];
        }
        else if(i%2==0){
            int p=min(to[0][i],to[0][i+1]);
            if(now>p){
                ans+=now-p;
                now=p;
            }
        }
        else{
            int p=max(to[1][i],to[1][i+1]);
            if(p>now){
                ans+=p-now;
                now=p;
            }
        }
    }
    ans+=lim;
    printf("%d\n",ans);
    return 0;
}

C 采蘑菇的摩托车

【分析】
题目比较清楚。题目求一个最小值,而且答案是具有单调性的,这就想到了二分。
二分时间。但是怎么判这种情况是否合法呢?如果摩托车只是横着或者竖着,当然是 O(n) 记录在数组中判一下。但是对角线怎么判呢?是否也能 O(n) 判呢?既然是对角线,假如外面有一个巨大的正方形,我能够算出这个点算出解析式y=x+k,然后知道最后落在哪。
敲完代码,又突然发现不用二分。。。直接for过来就好了。

#include 
using namespace std;
#define M 1000005
#define N 2000005
int X[M],Y[M],cnt1[N],cnt2[N];
int n,k;
int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++){
        int x,y;
        scanf("%d %d",&x,&y);
        X[x]++,Y[y]++;
        cnt1[x+y+M]++,cnt2[x-y+M]++;
        bool f=X[x]>=k|Y[y]>=k|cnt1[x+y+M]>=k|cnt2[x-y+M]>=k;
        if(f){
            printf("%d\n",i);
            return 0;
        }
    }
    puts("-1");
    return 0;
}

D 帅数

【分析】
都已经是D题了,这道题肯定不会这么简单!然后就开始打表找规律了。然后 然后就放弃了。
之后就当是模拟题写了,因为怎么搞都不会超时啊。因为我最讨厌的就是这种模拟题,所以我调了2个小时。(一共也才3个小时)。这也体现出我的代码功底还不到位。
这里贴的是我的一位学长的代码,以及他对模拟这题一些想法。

【代码】

/*
理解好题目后不妨称作是一道模拟题。
从前向后扫描这个数,如果遇到了一个位置与上一个位置的奇偶性相同,就开始修改。
修改共有两种方法,改大和改小,为了和原数尽可能接近,改大的数之后的部分用0和1交替补充,改小的用8和9交替补充。最后再用高精减比较距离。修改的过程中有一些特殊情况,比如进位等稍微考虑一下可以发现,存在进位,退位等情况的修改都不会得到更优的答案,所以可以忽略。

总体思路还是比较简单的,就是高精有点麻烦。
*/
const int M=1005,P=1e7;
int n,flag=-1;//0b 1s
int xp[10];
char A[M],B[M],S[M];
void make_big(){
    int p=1,k=(A[0]-'0')&1;
    B[0]=A[0];
    while(p1;
        if(((A[p]-'0')&1)!=k)break;
        B[p]=A[p];p++;
    }
    if(A[p]=='9'){flag=1;return;}
    if(p1;
    while(++p1;B[p]=k+'0'; }
}
void make_small(){
    int p=1,k=(A[0]-'0')&1;
    S[0]=A[0];
    while(p1;
        if(((A[p]-'0')&1)!=k)break;
        S[p]=A[p];p++;
    }
    if(A[p]=='0'){flag=0;return;}
    if(p1;
    while(++p1;S[p]=k+'8'; }
}
void init(){
    int t=1;
    for(int i=0;i<=7;i++){
        xp[i]=t;t*=10;
    }
}
struct BigInt{
    int num[150];
    BigInt(){memset(num,0,sizeof(num));}
    void push(char *str){
        for(int i=0;iint t=str[n-i]-'0';
            num[i/8]+=xp[i%8]*t;//压位
        }
    }
};
BigInt minus(BigInt a,BigInt b){
    BigInt res;
    for(int i=0;i<150;i++){
        res.num[i]=a.num[i]-b.num[i];
        if(res.num[i]<0){
            res.num[i]+=P;a.num[i+1]-1;
            int k=i+1;while(k<150&&a.num[k]<0)a.num[k++]+=P;
        }
    }return res;
}
int cmp(BigInt a,BigInt b){
    for(int i=149;i>=0;i--){
        if(a.num[i]>b.num[i])return 1;
        if(a.num[i]return -1;
    }return 0;
}
bool check(){
    if(flag!=-1)return flag;
    BigInt a,b,s;
    a.push(A);b.push(B);s.push(S);
    int t=cmp(minus(b,a),minus(a,s));
    if(!t)printf("%s ",S);
    if(t>0)return 1;
    else return 0;
}
int main(){
    init();
    gets(A);n=strlen(A);
    make_big();make_small();
    if(check())printf("%s\n",S);
    else printf("%s\n",B);
    return 0;
}

E 苹果落地

【分析】
这题比赛的时候没有写出来真的是太失误了,因为我们做过类似的!
然而因为浪费了大多数时间在D题上,所以最后没有几分钟来调代码了,只是交了最暴力的50分的代码。
我们有这样一个习惯,比如枚举一维,贪心另一维。对于一个点(x,y),我们可以枚举答案是在第i行,那么答案一定产生在最靠近y的地方,那么我们可以对所有的点进行预处理,l[i][j]表示在第i行,列小于等于j的最大值。r[i][j]也就同理。那么答案一定是从l[i][y]和r[i][y]中产生的。
至于要加入(x,y)这个点也只需要将x行的数组更新一遍就好了。
最后的复杂度就是 O((n+m)q) 了。

【代码】

#include 
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
#define M 505
int l[M][M];
int r[M][M];
int n,m,g;
char a[M];
void Max(int &x,int y){
    if(x==-1||xvoid Min(int &x,int y){
    if(x==-1||x>y)x=y;
}
int main(){
    mem(l,-1);
    mem(r,-1);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",a+1);
        for(int j=1;j<=m;j++){
            if(a[j]!='x')continue;
            for(int k=j;k<=m;k++)Max(l[i][k],j);
            for(int k=1;k<=j;k++)Min(r[i][k],j);
        }
    }
    scanf("%d",&g);
    while(g--){
        int x,y;
        scanf("%d %d",&x,&y);
        int d=-1;
        for(int i=1;i<=n;i++){
            int p=l[i][y];
            if(p!=-1)Min(d,(x-i)*(x-i)+(y-p)*(y-p));
            int q=r[i][y];
            if(q!=-1)Min(d,(x-i)*(x-i)+(y-q)*(y-q));
        }
        printf("%d\n",d);
        for(int i=y;i<=m;i++)Max(l[x][i],y);
        for(int i=1;i<=y;i++)Min(r[x][i],y);
    }
    return 0;
}

我之前说的类似的题目是有很多圆和很多点,求有多少个点没有被圆覆盖。那道题是用multiset来维护的。

【代码】

#include 
using namespace std;
multiset<int>st[100005];
int n,m,x,y,R,cnt;
int main(){
    scanf("%d",&n);
    cnt=n;
    for(int i=1;i<=n;i++){
        scanf("%d %d",&x,&y);
        st[y].insert(x);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&x,&y,&R);
        int s=max(0,y-R);
        int t=min(y+R,10000);
        for(int j=s;j<=t;j++){
            multiset<int>::iterator it,it1;
            int h=y-j;
            int d=sqrt(R*R-h*h);
            int l=max(x-d,0),r=min(x+d,10000);
            it=st[j].lower_bound(l);
            while(it!=st[j].end()&&*it<=r){
                it1=it;
                it1++;
                st[j].erase(it);
                it=it1;
                cnt--;
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

F 疯狂原始人

比赛的时候还是看了一下题目的。然后就蒙逼了。
赛后就学习了AC自动机和fail树。
我所理解的AC自动机,就是我们在对一个母串匹配的一个巨大的优化:
我们在通常匹配的时候就是两层for,只要一不满足,母串的i++,子串的j=0,重新进行匹配,这样之前所做的努力就全部清空了。我们所希望的,就是子串之前已经匹配出的串能在母串中找到相同的地方,然后继续匹配。
那么AC自动机和fail树做的就是这个工作了。

然后,对于fail上的一个节点,如果它有一个子孙在自动机上走的时候被遍历到了,那么这个点权值也需要增加。而且一个点被遍历了一次,那么它的所有祖先权值都需要增加,这里可以使用树状数组+dfs序来解决。但是如果一个点有多个子孙都被遍历到了,那么这个点的权值就会出错。
对于一个点,它如果有多个子孙被遍历过,应该只算1次。
观察每两个点,他们的LCA及以上就应当被减去重复的权值,同样使用树状数组+DFS序。
但是如果每两个点都减一遍的话肯定又会多减去很多权值。
再仔细观察之后发现,对于一个节点内所有被遍历的儿子,只需要把每两个相邻的儿子LCA及以上减去权值就好了。
最后询问的时候,我们找到询问串的末尾是那一个节点,询问这个节点及其子树内的权值和即可。
小C说这道题很巧妙,因为在敲完Build_Trie、Build_AC、Build_Fail之后,这道题才刚刚开始。因为这里要用到fail树的性质、dfs序、LCA以及树状数组的更新和查询。
因为对于第一次接触AC自动机和fail树,而且fail树的这个性质特别的玄学,博主真的不能展开讲,所以大家还是在网上搜索吧。估计搜不到,我这里只讲这道题的fail树怎么处理。

【代码】

#include 
#define lowbit(x) x&-x
using namespace std;
#define M 2000005
int ls[M],rs[M],dep[M];
int n,q,id,dfn,hav;
int pos[100005];
int pre[M][26];
int fa[25][M];
char str[M];
int two[25];
int fail[M];
int head[M];
int Q[M];
struct node{
    int to,nx;
}e[M];
struct Bit{
    int num[M];
    Bit(){
        memset(num,0,sizeof(num));
    }
    void add(int x,int v){
        while(x<=id+1){
            num[x]+=v;
            x+=lowbit(x);
        }
    }
    int query(int x){
        int res=0;
        while(x){
            res+=num[x];
            x-=lowbit(x);
        }
        return res;
    }
}T;
void Add_edge(int p,int f){
    fa[0][p]=f;
    e[++hav]=(node){p,head[f]},head[f]=hav;
}
void Build_AC(){
    int l=0,r=-1;
    for(int i=0;i<26;i++){
        if(pre[0][i])Q[++r]=pre[0][i];
    }
    while(l<=r){
        int x=Q[l++];
        Add_edge(x,fail[x]);
        for(int i=0;i<26;i++){
            int t=pre[x][i];
            if(t){
                Q[++r]=t;
                fail[t]=pre[fail[x]][i];
            }else pre[x][i]=pre[fail[x]][i];
        }
    }
}
void dfs(int x,int d){//Build_faild
    ls[x]=++dfn;
    dep[x]=d;
    for(int i=head[x];i;i=e[i].nx){
        int to=e[i].to;
        dfs(to,d+1);
    }
    rs[x]=dfn;
}
int LCA(int x,int y){
    if(dep[y]>dep[x])swap(x,y);
    for(int i=0;i<22;i++){
        if(two[i]&(dep[x]-dep[y]))x=fa[i][x];
    }
    if(x==y)return x;
    for(int i=21;i>=0;i--){
        if(fa[i][x]!=fa[i][y]){
            x=fa[i][x];
            y=fa[i][y];
        }
    }
    return fa[0][x];
}
bool cmp(int x,int y){
    return ls[x]int now[M];
void Run_word(){
    scanf("%s",str);
    int len=strlen(str);
    int sz=0,cur=0;
    for(int i=0;iint c=str[i]-'a';
        cur=pre[cur][c];
        if(cur)now[sz++]=cur;
    }
    sort(now,now+sz,cmp);
    for(int i=0;i1);
        if(i)T.add(ls[LCA(now[i],now[i-1])],-1);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=0;i<=22;i++)two[i]=1<for(int i=1;i<=n;i++){
        scanf("%s",str);
        int cur=0;
        int len=strlen(str);//Build_Trie
        for(int j=0;jint c=str[j]-'a';
            if(!pre[cur][c])pre[cur][c]=++id;
            cur=pre[cur][c];
        }
        pos[i]=cur;
    }
    Build_AC();
    dfs(0,0);
    for(int i=1;i<=20;i++){
        for(int j=1;j<=id;j++){
            fa[i][j]=fa[i-1][fa[i-1][j]];
        }
    }
    scanf("%d",&q);
    while(q--){
        int f;
        scanf("%d",&f);
        if(f==1)Run_word();
        else{
            int x;
            scanf("%d",&x);
            int Id=pos[x];
            printf("%d\n",T.query(rs[Id])-T.query(ls[Id]-1));
        }
    }
    return 0;
}

这场比赛如果策略得当的话,是能A掉4题。
但是又一次地死在了代码量大的模拟题上,我最不擅长的模拟题。
这也许是我的compare大法使用过多的后遗症吧。其实compare大法是既耗时,又耗脑,还没有好处的事情。因为你没有自己调过那些小细节。既然没有真正调出那些小细节,那就说明你没有完全把握住那些小细节。下次比赛中随便再来一个小细节,你没有调出来,那么题目就过不掉,也没有别人的代码给你比较。
我以后再想用compare大法的时候,我应该要想起曾经这些比赛摔的跤,自己扎扎实实地独立地调出代码。
同时,敲代码的时候绝对不能想到什么就敲什么,一些题目的小细节一定要提前做好规划,既能精简思维,又能提高敲代码的速度,做到显示屏上无代码,而心中有代码。
愿自己以后能够快速地A掉万恶的模拟题。

你可能感兴趣的:(2017.5.17 COCI2014/2015 Contest#5)