NOIP2017模拟赛(十四)总结

来源:互联网 发布:淘宝体检中心进不去 编辑:程序博客网 时间:2024/05/29 10:21

由于特殊原因,从接下来开始NOIP2017模拟赛的总结将不再显示题面。这意味着可能只有队友和老师能够看到总结内容的全部,不便之处敬请原谅。


T1:最大得分(SMOJ1964)

题目分析:这题看上去没那么容易,但仔细想想还是挺简单的。首先发现答案不可能超过7,于是我们就枚举答案。假设原串长度为12,枚举答案为2,则原串可以抽象成下面的样子:
1 2 3 3 2 1 1 2 3 3 2 1
其中标相同数字的地方最后要变成相同字母。接下来就是个很显然的贪心了。假设’a’在四个1的地方出现次数最多,那就把他们都变成’a’,并计算代价……。最后用总代价和maxChanges比较即可。其实这题还可以二分答案,不过我认为没有必要。

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxl=55;const int maxc=30;int cnt[maxc];int id[maxl][maxl];int cur;char s[maxl];int a[maxl];int c;int Get(){    int temp=0;    for (int i=1; i<=cur; i++)    {        memset(cnt,0,sizeof(cnt));        int num=id[i][0];        for (int j=1; j<=num; j++) cnt[ a[ id[i][j] ] ]++;        int k=num;        for (int j=0; j<maxc; j++) k=min(k,num-cnt[j]);        temp+=k;    }    return temp;}int main(){    freopen("1964.in","r",stdin);    freopen("1964.out","w",stdout);    scanf("%s",&s);    scanf("%d",&c);    int slen=strlen(s);    for (int i=0; i<slen; i++) a[i+1]=s[i]-'a';    int max_ans=1,temp=slen;    while ( !(temp&1) ) max_ans++,temp>>=1;    int T=slen/temp;    cur=(temp+1)>>1;    for (int i=0; i<T; i++)    {        int h=i*temp;        int j;        for (j=1; (j<<1)<=temp; j++)        {            id[j][ ++id[j][0] ]=h+j;            id[j][ ++id[j][0] ]=h+temp-j+1;        }        id[j][ ++id[j][0] ]=h+j;    }    if ( Get()<=c )    {        printf("%d\n",max_ans);        return 0;    }    for (int i=max_ans-1; i>=1; i--)    {        cur=slen>>i;        temp=cur<<1;        T=slen/temp;        memset(id,0,sizeof(id));        for (int j=0; j<T; j++)        {            int h=j*temp;            int k;            for (k=1; (k<<1)<=temp; k++)            {                id[k][ ++id[k][0] ]=h+k;                id[k][ ++id[k][0] ]=h+temp-k+1;            }        }        if ( Get()<=c )        {            printf("%d\n",i);            return 0;        }    }    printf("0\n");    return 0;}

T2:寄存器(SMOJ1965)

题目分析:对于这种看上去很玄学的题目,我们一般要分析出一些特征和性质,才能使问题简单化。①这题首先有一个结论:如果我已经知道了最后的X和Y,我是可以倒推出操作序列的。如果X>Y,上一步的两个数一定是X-Y和Y。②从上面的结论我们也可以看出,最后的X和Y必定互质,否则在倒推的过程中就会出现X=Y且X,Y都不为1的情况,这和题意不符。③当然还有一个小结论:最后的Y一定小于X(这不是废话吗)。
那么我们枚举Y,然后用类似求Gcd的方法计算出从(1,1)到(X,Y)所需要的步数。由于Gcd是log( log(n) )的,可以证明做这一步的时间接近线性(事实上我考试的时候还在担心会不会超时来着)。然后就是要处理字典序问题。如果我们在做上面这一步的时候一边Gcd一边生成字典序,肯定超时。我们不妨先将最少步数step求出来,并用一个数组记录需要操作step步的那些Y值。直觉告诉我最后的step值不会很大,那些Y的个数也不会很多(事实上X=106的时候step也只有三十多,根本不虚)。这个时候我们再把字典序拎出来暴力比较就好。

CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=100000;int num[maxn];int val,cur;int ans[maxn];int temp[maxn];int len;int G,R;int now;int Gcd(int x,int y){    if (!y) return x;    return Gcd(y,x%y);}int Get(int x,int y){    if (!y) return -1;    return x/y+Get(y,x%y);}void Work(int x,int y){    if (!y) return;    while (x>=y) x-=y,temp[++len]=now;    now=1-now;    Work(y,x);}void Check(){    for (int i=1; i<=val; i++)    {        if (temp[i]<ans[i]) break;        if (ans[i]<temp[i]) return;    }    for (int i=1; i<=val; i++) ans[i]=temp[i];}int main(){    freopen("1965.in","r",stdin);    freopen("1965.out","w",stdout);    scanf("%d",&G);    while (G--)    {        scanf("%d",&R);        val=R-1;        cur=1;        num[1]=1;        for (int i=2; i<R; i++)            if ( Gcd(R,i)==1 )            {                int v=Get(R,i);                if (v==val) num[++cur]=i;                else                    if (v<val) val=v,cur=1,num[1]=i;            }        for (int i=1; i<=val; i++) ans[i]=1;        for (int i=1; i<=cur; i++)        {            len=now=0;            Work(R,num[i]);            for (int j=1; (j<<1)<=val; j++) swap(temp[j],temp[val-j+1]);            Check();        }        for (int i=1; i<=val; i++) printf("%c",'X'+ans[i]);        printf("\n");    }    return 0;}

T3:肯德基(SMOJ1966)

题目分析:我比赛的时候写了个O(r3)的暴力还写错了……
我们分析一下就能得到一个结论:如果我们要从第i行的某个点转移到第j行的某个点,我们可以先直线走到(i,1),再沿某条可能很曲折的路线走到(j,1),然后直接到达目标点;还可以走s>(i,c)>(j,1)>t;还可以走s>(i,1)>(j,c)>t;走s>(i,c)>(j,c)>t。如果i=j,还可以考虑直接走过去。直线走的那部分直接用部分和可以得到,现在的关键是求出(x,1)(y,c)之间的最短路径(1<=x,y<=r)
如果这一个部分直接用多源最短路算法做,我们就要对原图重新建边。将原图中最左边一排的格子标号为1~r,最右边的一排格子标号为r+1~2r,然后在ii+1r+ir+i+1之间连两条有向边(1<=i<r),有向边的边权等于它所指向的那个点的点权。ir+i之间的连边也类似,只不过要多计算上中间那些点的点权和而已,这个用部分和就行。这样重新构图之后,原图点i到点j路径上的点权和等于新图中点i到点j路径上的边权和+点i的权值。然后用多源最短路的Floyd即可。(然而我考试的时候觉得重新建边太麻烦,写了个同样是立方级别的玄学DP,结果只有30)
后来我回家考虑了一下,新图的边数只有6r,如果我用Dijkstra+Heap的话,时间复杂度就是O(r2log(r))的,我写了一下:80分,多了一个点,用了1.7s。然后我又试了一下SPFA,结果AC了……
老师说100分要用DP,然而我不知道要按什么顺序去做,想了好久都想不出来。直到kekxy神犇告诉我他是用DP的……这题的难点在于某两个点之间的最短路可能是蛇形的:

但有一个性质:从ir+i一定只会拐一次弯,多次转弯一定不优:

那我们不妨枚举i,然后枚举转弯处第j行,先求得从ir+i的最短路,然后我们考虑从(i,1)(j,1)的路径的最后一步:

(注意,以上三条路径在省略号处可能是很曲折的)
到达(j,1)的可能是红色或蓝色路径,这样它必定经过(j,c),且到达(j,c)的前一步必定是(j1,c),那么就可以转化成(i,1)>(j1,c)(j,c)>(j,1)两个子问题,前者通过控制DP的顺序可以预先求出,后者我们已经算出。如果走绿色路径,最后一步就是(j1,1),这也是可以预先求出的。这样DP的顺序为:枚举第i行,然后从第i行开始向两边按顺序扩展求最短路。其他点对之间的路径也可以类似地DP,这样时间复杂度就是O(n2)

CODE(Dijkstra+Heap):

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxr=2010;const int maxc=210;const int oo=1000000001;struct edge{    int obj,len;    edge *Next;} e[maxr*6];edge *head[maxr<<1];int cur=-1;int Heap[maxr<<1];int id[maxr<<1];int tail,s;int dis[maxr<<1][maxr<<1];int lsum[maxr];int rsum[maxr];int hsum[maxr][maxc];int t[maxr][maxc];int r,c,d;int Min(int x,int y){    if (x<y) return x;    return y;} int Get(int lx,int ly,int nx,int ny){    int temp=oo,k=oo;    if (lx==nx)    {        k=-t[lx][ly];        k+=(hsum[nx][ max(ly,ny) ]-hsum[lx][ min(ly,ny)-1 ]);    }    int lleft=hsum[lx][ly]-t[lx][1],lright=hsum[lx][c]-hsum[lx][ly-1]-t[lx][c];    int nleft=hsum[nx][ny]-t[nx][1],nright=hsum[nx][c]-hsum[nx][ny-1]-t[nx][c];    temp=Min(temp, lleft+dis[lx][nx]+nleft );    temp=Min(temp, lright+dis[r+lx][r+nx]+nright );    temp=Min(temp, lleft+dis[lx][r+nx]+nright );    temp=Min(temp, lright+dis[r+lx][nx]+nleft );    return Min(temp-t[lx][ly],k);}void Preparation(){    lsum[1]=t[1][1];    rsum[1]=t[1][c];    for (int i=2; i<=r; i++)    {        lsum[i]=lsum[i-1]+t[i][1];        rsum[i]=rsum[i-1]+t[i][c];    }    for (int i=1; i<=r; i++)    {        hsum[i][1]=t[i][1];        for (int j=2; j<=c; j++)            hsum[i][j]=hsum[i][j-1]+t[i][j];    }}void Add(int x,int y,int z){    cur++;    e[cur].obj=y;    e[cur].len=z;    e[cur].Next=head[x];    head[x]=e+cur;}void Add_edge(){    for (int i=1; i<=(r<<1); i++) head[i]=NULL;    for (int i=1; i<r; i++)    {        Add(i,i+1,t[i+1][1]);        Add(i+1,i,t[i][1]);        Add(r+i,r+i+1,t[i+1][c]);        Add(r+i+1,r+i,t[i][c]);    }    for (int i=1; i<=r; i++)    {        Add(i,r+i,hsum[i][c]-t[i][1]);        Add(r+i,i,hsum[i][c-1]);    }}void Swap(int &x,int &y){    int z=x;    x=y;    y=z;}int Get(){    int temp=Heap[1];    Heap[1]=Heap[tail];    id[ Heap[1] ]=1;    tail--;    int x=1;    while (1)    {        int y=x,Left=x<<1,Right=Left|1;        if ( Left<=tail && dis[s][ Heap[Left] ]<dis[s][ Heap[y] ] ) y=Left;        if ( Right<=tail && dis[s][ Heap[Right] ]<dis[s][ Heap[y] ] ) y=Right;        if (y==x) break;        Swap(Heap[x],Heap[y]);        Swap(id[ Heap[x] ],id[ Heap[y] ]);        x=y;    }    return temp;}void Update(int x){    while (x>1)    {        int y=x>>1;        if (dis[s][ Heap[y] ]<=dis[s][ Heap[x] ]) break;        Swap(Heap[x],Heap[y]);        Swap(id[ Heap[x] ],id[ Heap[y] ]);        x=y;    }}void Release(int x,int y,int l){    if (dis[s][x]+l>=dis[s][y]) return;    dis[s][y]=dis[s][x]+l;    Update(id[y]);}void Dijkstra(int x){    s=x;    for (int i=1; i<=(r<<1); i++) dis[s][i]=oo;    dis[s][s]=0;    tail=1;    Heap[1]=s;    for (int i=1; i<=(r<<1); i++)        if (i!=s) Heap[++tail]=i,id[i]=tail;    for (int i=1; i<(r<<1); i++)    {        x=Get();        for (edge *p=head[x]; p; p=p->Next)            Release(x,p->obj,p->len);    }}int main(){    freopen("1966.in","r",stdin);    freopen("1966.out","w",stdout);    scanf("%d%d",&r,&c);    for (int i=1; i<=r; i++)        for (int j=1; j<=c; j++)            scanf("%d",&t[i][j]);    Preparation();    Add_edge();    for (int i=1; i<=(r<<1); i++) Dijkstra(i);    for (int i=1; i<=r; i++)        for (int j=1; j<=(r<<1); j++)        {            dis[i][j]+=t[i][1];            dis[r+i][j]+=t[i][c];        }    scanf("%d",&d);    long long ans=t[1][1];    int lx=1,ly=1;    for (int i=1; i<=d; i++)    {        int x,y;        scanf("%d%d",&x,&y);        ans+=Get(lx,ly,x,y);        lx=x;        ly=y;    }    printf("%lld\n",ans);    return 0;}

CODE(SPFA):

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxr=2010;const int maxc=210;const int oo=2000000001;struct edge{    int obj,len;    edge *Next;} e[maxr*6];edge *head[maxr<<1];int cur=-1;bool vis[maxr<<1];int que[maxr<<1];int he,ta;int dis[maxr<<1][maxr<<1];int lsum[maxr];int rsum[maxr];int hsum[maxr][maxc];int t[maxr][maxc];int r,c,d;int Min(int x,int y){    if (x<y) return x;    return y;} int Get(int lx,int ly,int nx,int ny){    int temp=oo,k=oo;    if (lx==nx)    {        k=-t[lx][ly];        k+=(hsum[nx][ max(ly,ny) ]-hsum[lx][ Min(ly,ny)-1 ]);    }    int lleft=hsum[lx][ly]-t[lx][1],lright=hsum[lx][c]-hsum[lx][ly-1]-t[lx][c];    int nleft=hsum[nx][ny]-t[nx][1],nright=hsum[nx][c]-hsum[nx][ny-1]-t[nx][c];    temp=Min(temp, lleft+dis[lx][nx]+nleft );    temp=Min(temp, lright+dis[r+lx][r+nx]+nright );    temp=Min(temp, lleft+dis[lx][r+nx]+nright );    temp=Min(temp, lright+dis[r+lx][nx]+nleft );    return Min(temp-t[lx][ly],k);}void Preparation(){    lsum[1]=t[1][1];    rsum[1]=t[1][c];    for (int i=2; i<=r; i++)    {        lsum[i]=lsum[i-1]+t[i][1];        rsum[i]=rsum[i-1]+t[i][c];    }    for (int i=1; i<=r; i++)    {        hsum[i][1]=t[i][1];        for (int j=2; j<=c; j++)            hsum[i][j]=hsum[i][j-1]+t[i][j];    }}void Add(int x,int y,int z){    cur++;    e[cur].obj=y;    e[cur].len=z;    e[cur].Next=head[x];    head[x]=e+cur;}void Add_edge(){    for (int i=1; i<=(r<<1); i++) head[i]=NULL;    for (int i=1; i<r; i++)    {        Add(i,i+1,t[i+1][1]);        Add(i+1,i,t[i][1]);        Add(r+i,r+i+1,t[i+1][c]);        Add(r+i+1,r+i,t[i][c]);    }    for (int i=1; i<=r; i++)    {        Add(i,r+i,hsum[i][c]-t[i][1]);        Add(r+i,i,hsum[i][c-1]);    }}void Swap(int &x,int &y){    int z=x;    x=y;    y=z;}void SPFA(int s){    for (int i=1; i<=(r<<1); i++) dis[s][i]=oo,vis[i]=false;    dis[s][s]=0,vis[s]=true;    he=0,ta=1,que[1]=s;    while (he!=ta)    {        he=(he+1)%(maxr<<1);        int node=que[he];        for (edge *p=head[node]; p; p=p->Next)        {            int son=p->obj;            if (dis[s][node]+p->len<dis[s][son])            {                dis[s][son]=dis[s][node]+p->len;                if (!vis[son])                {                    vis[son]=true;                    ta=(ta+1)%(maxr<<1);                    que[ta]=son;                }            }        }        vis[node]=false;        int nhe=(he+1)%(maxr<<1);        if (dis[s][ que[ta] ]<dis[s][ que[nhe] ]) Swap(que[ta],que[nhe]);    }}int main(){    freopen("1966.in","r",stdin);    freopen("1966.out","w",stdout);    scanf("%d%d",&r,&c);    for (int i=1; i<=r; i++)        for (int j=1; j<=c; j++)            scanf("%d",&t[i][j]);    Preparation();    Add_edge();    for (int i=1; i<=(r<<1); i++) SPFA(i);    for (int i=1; i<=r; i++)        for (int j=1; j<=(r<<1); j++)        {            dis[i][j]+=t[i][1];            dis[r+i][j]+=t[i][c];        }    scanf("%d",&d);    long long ans=t[1][1],lx=1,ly=1;    for (int i=1; i<=d; i++)    {        int x,y;        scanf("%d%d",&x,&y);        ans+=Get(lx,ly,x,y);        lx=x;        ly=y;    }    printf("%lld\n",ans);    return 0;}

CODE(DP):

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxr=2010;const int maxc=210;const int oo=1000000001;int dis[maxr<<1][maxr<<1];int lsum[maxr];int rsum[maxr];int hsum[maxr][maxc];int t[maxr][maxc];int r,c,d;int Min(int x,int y){    if (x<y) return x;    return y;}int Get(int lx,int ly,int nx,int ny){    int temp=oo,k=oo;    if (lx==nx)    {        k=-t[lx][ly];        k+=(hsum[nx][ max(ly,ny) ]-hsum[lx][ Min(ly,ny)-1 ]);    }    int lleft=hsum[lx][ly]-t[lx][1],lright=hsum[lx][c]-hsum[lx][ly-1]-t[lx][c];    int nleft=hsum[nx][ny]-t[nx][1],nright=hsum[nx][c]-hsum[nx][ny-1]-t[nx][c];    temp=Min(temp, lleft+dis[lx][nx]+nleft );    temp=Min(temp, lright+dis[r+lx][r+nx]+nright );    temp=Min(temp, lleft+dis[lx][r+nx]+nright );    temp=Min(temp, lright+dis[r+lx][nx]+nleft );    return Min(temp-t[lx][ly],k);}void Preparation(){    lsum[1]=t[1][1];    rsum[1]=t[1][c];    for (int i=2; i<=r; i++)    {        lsum[i]=lsum[i-1]+t[i][1];        rsum[i]=rsum[i-1]+t[i][c];    }    for (int i=1; i<=r; i++)    {        hsum[i][1]=t[i][1];        for (int j=2; j<=c; j++)            hsum[i][j]=hsum[i][j-1]+t[i][j];    }}void DP(){    for (int i=1; i<=r; i++)        dis[i][r+i]=dis[r+i][i]=hsum[i][c];    for (int i=1; i<=r; i++)    {        for (int j=1; j<i; j++)        {            int temp=hsum[j][c]+(lsum[i]-lsum[j])+(rsum[i]-rsum[j]);            dis[i][r+i]=Min(dis[i][r+i],temp);            dis[r+i][i]=Min(dis[r+i][i],temp);        }        for (int j=i+1; j<=r; j++)        {            int temp=hsum[j][c]+(lsum[j-1]-lsum[i-1])+(rsum[j-1]-rsum[i-1]);            dis[i][r+i]=Min(dis[i][r+i],temp);            dis[r+i][i]=Min(dis[r+i][i],temp);        }        dis[i][i]=t[i][1];        dis[r+i][r+i]=t[i][c];    }    for (int i=1; i<=r; i++)    {        for (int j=i-1; j>=1; j--)        {            dis[i][j]=Min(dis[i][j+1]+t[j][1],dis[i][r+j+1]+dis[r+j][j]);            dis[i][r+j]=Min(dis[i][r+j+1]+t[j][c],dis[i][j+1]+dis[j][r+j]);            dis[r+i][j]=Min(dis[r+i][j+1]+t[j][1],dis[r+i][r+j+1]+dis[r+j][j]);            dis[r+i][r+j]=Min(dis[r+i][r+j+1]+t[j][c],dis[r+i][j+1]+dis[j][r+j]);        }        for (int j=i+1; j<=r; j++)        {            dis[i][j]=Min(dis[i][j-1]+t[j][1],dis[i][r+j-1]+dis[r+j][j]);            dis[i][r+j]=Min(dis[i][r+j-1]+t[j][c],dis[i][j-1]+dis[j][r+j]);            dis[r+i][j]=Min(dis[r+i][j-1]+t[j][1],dis[r+i][r+j-1]+dis[r+j][j]);            dis[r+i][r+j]=Min(dis[r+i][r+j-1]+t[j][c],dis[r+i][j-1]+dis[j][r+j]);        }    }}int main(){    freopen("1966.in","r",stdin);    freopen("1966.out","w",stdout);    scanf("%d%d",&r,&c);    for (int i=1; i<=r; i++)        for (int j=1; j<=c; j++)            scanf("%d",&t[i][j]);    Preparation();    DP();    scanf("%d",&d);    long long ans=t[1][1];    int lx=1,ly=1;    for (int i=1; i<=d; i++)    {        int x,y;        scanf("%d%d",&x,&y);        ans+=Get(lx,ly,x,y);        lx=x;        ly=y;    }    printf("%I64d\n",ans);    return 0;}

总结:这次比赛虽然A了前两题,但第三题只有30分,连O(n3)的暴力分都没拿到。我在做第三题的时候时间还是比较充裕的,还有80min。然而我没有把一些细节的地方考虑清楚,而且一直在想O(n2)的做法,拖延了很久才开始写代码。导致最后时间有点赶(另外我看错了比赛结束时间,我以为是5:30其实是5:50,那多出来的20分钟我也没有将我的代码改到更高分),代码出现了一些漏洞,比如两个点在同一行的情况没有特判。以后要留够充足的时间码code,实在想不到正解就要果断转去写代码。

原创粉丝点击