第五次上机赛解题报告及标程

来源:互联网 发布:二进制转bcd码算法 编辑:程序博客网 时间:2024/05/21 17:33

  这次上机的题目考查的内容以图论的一些基础算法为主,我实话实话这次上机题有出失误的地方,个别题的难度没有控制好,所以过题情况不佳也是情理之中。

  A. 对称二叉树

  与上一次上机的同构二叉树很相似,这不过这道题要求的是轴对称;事实上也只要在那道题的基础上改动几处代码就可以了。

#include<cstdio>#include<cstdlib>#include<cstring>using namespace std;const int MAXN=1005;struct BTNode{    char data[10];    BTNode *lchild,*rchild;};char str[MAXN];void CreateBTNode(BTNode *&b){    BTNode *St[MAXN],*p;    int top=-1,k,l=strlen(str);    b=NULL;    for(int i=0; i<l; ++i)        switch(str[i])        {        case '(':            St[++top]=p;            k=1;            break;        case ')':            --top;            break;        case ',':            k=2;            break;        default:            p=(BTNode *)malloc(sizeof(BTNode));            int j=0;            while(i<l&&str[i]!='('&&str[i]!=')'&&str[i]!=',')                p->data[j++]=str[i++];            --i;            p->data[j]='\0';            p->lchild=p->rchild=NULL;            if(!b)                b=p;            else                switch(k)                {                case 1:                    St[top]->lchild=p;                    break;                case 2:                    St[top]->rchild=p;                    break;                }        }}bool Symm(BTNode *b1,BTNode *b2){    if(!b1&&!b2)        return true;    if(!b1||!b2)        return false;    if(strcmp(b1->data,b2->data)!=0)        return false;    return Symm(b1->lchild,b2->rchild)&&Symm(b1->rchild,b2->lchild);}bool Symmtree(BTNode *b){    if(!b)        return true;    return Symm(b->lchild,b->rchild);}void DestroyBT(BTNode *&b){    if(b->lchild)        DestroyBT(b->lchild);    if(b->rchild)        DestroyBT(b->rchild);    free(b);}int main(){    while(~scanf("%s",str))    {        BTNode *b;        CreateBTNode(b);        puts(Symmtree(b)?"YES":"NO");        DestroyBT(b);    }}

  B. 拮据的模拟城市

  这道题的题意其实非常明确,就是要求最小生成树的各边权值和。唯一要注意的是图中可以有重边,所以使用邻接矩阵建图时需要注意。

  这里我给出kruskal和prim两种做法的标程。

#include<cstdio>#include<algorithm>using namespace std;const int MAXN=1005;const int MAXM=100005;struct edge{    int u,v,w;    edge(int _u=0,int _v=0,int _w=0):u(_u),v(_v),w(_w) {}    bool operator<(const edge &oth) const    {        return w<oth.w;    }} e[MAXM];int n,m;int u[MAXN];void init(){    for(int i=1; i<=n; ++i)        u[i]=i;}int find(int x){    if(u[x]!=x)        u[x]=find(u[x]);    return u[x];}void merge(int x,int y){    u[find(x)]=find(y);}int kruskal(){    sort(e,e+m);    int ret=0,cnt=0;    init();    for(int i=0; cnt<n-1&&i<m; ++i)        if(find(e[i].u)!=find(e[i].v))        {            merge(e[i].u,e[i].v);            ret+=e[i].w;            ++cnt;        }    return ret;}int main(){    while(~scanf("%d%d",&n,&m))    {        for(int i=0; i<m; ++i)            scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);        printf("%d\n",kruskal());    }}
#include<cstdio>#include<cstring>using namespace std;const int MAXN=1005;const int INF=0x3f3f3f3f;int g[MAXN][MAXN],n,lowc[MAXN];int prim(){    for(int i=1; i<=n; ++i)        lowc[i]=g[1][i];    int ret=0;    for(int i=1; i<n; ++i)    {        int mark=-1,minc=INF;        for(int j=1; j<=n; ++j)            if(lowc[j]&&minc>lowc[j])            {                minc=lowc[j];                mark=j;            }        ret+=minc;        lowc[mark]=0;        for(int j=1; j<=n; ++j)            if(lowc[j]&&lowc[j]>g[mark][j])                lowc[j]=g[mark][j];    }    return ret;}int main(){    int m,u,v,l;    while(~scanf("%d%d",&n,&m))    {        memset(g,0x3f,sizeof(g));        for(int i=1; i<=n; ++i)            g[i][i]=0;        while(m--)        {            scanf("%d%d%d",&u,&v,&l);            if(l<g[u][v])                g[u][v]=g[v][u]=l;        }        printf("%d\n",prim());    }}

  C. 迷宫里有一只薛定谔的猫!

  这道题要求的,是从1点到其余所有点的最短距离的期望。所以跑一遍单源最短路就可以了,要注意的依然是重边问题。

  关于单源最短路,书中只给出了dijkstra算法,事实上这不是唯一的单源最短路算法。我这里给出dijkstra+邻接矩阵和spfa+邻接表的两种标程。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN=505;const int INF=0x3f3f3f3f;int g[MAXN][MAXN],dist[MAXN],n;bool vis[MAXN];void dijkstra(int src){    memset(dist,0x3f,sizeof(dist));    memset(vis,false,sizeof(vis));    dist[src]=0;    for(int i=0; i<n; ++i)    {        pair<int,int> tmp=make_pair(INF,-1);        for(int j=1; j<=n; ++j)            if(!vis[j]&&dist[j]<tmp.first)                tmp=make_pair(dist[j],j);        if(!~tmp.second)            break;        vis[tmp.second]=true;        for(int j=1; j<=n; ++j)            dist[j]=min(dist[j],tmp.first+g[tmp.second][j]);    }}int main(){    int m,a,b,l;    while(~scanf("%d%d",&n,&m))    {        memset(g,0x3f,sizeof(g));        for(int i=1; i<=n; ++i)            g[i][i]=0;        while(m--)        {            scanf("%d%d%d",&a,&b,&l);            g[a][b]=min(g[a][b],l);        }        dijkstra(1);        double ans=0;        for(int i=1; i<=n; ++i)            ans+=dist[i];        printf("%.2f\n",ans/(n-1));    }}
#include<cstdio>#include<cstring>#include<queue>using namespace std;const int MAXN=505;const int MAXM=MAXN*MAXN;struct graph{    int head[MAXN];    int to[MAXM];    int next[MAXM];    int len[MAXM];    int tot;    void init()    {        tot=0;        memset(head,0xff,sizeof(head));    }    void add(int u,int v,int w)    {        to[tot]=v;        len[tot]=w;        next[tot]=head[u];        head[u]=tot++;    }} g;int dist[MAXN];bool inque[MAXN];void spfa(int src){    memset(dist,0x3f,sizeof(dist));    memset(inque,false,sizeof(inque));    dist[src]=0;    queue<int> q;    q.push(src);    inque[src]=true;    while(!q.empty())    {        int u=q.front();        q.pop();        inque[u]=false;        for(int i=g.head[u]; ~i; i=g.next[i])        {            int v=g.to[i];            if(dist[u]+g.len[i]<dist[v])            {                dist[v]=dist[u]+g.len[i];                if(!inque[v])                {                    q.push(v);                    inque[v]=true;                }            }        }    }}int main(){    int n,m,a,b,l;    while(~scanf("%d%d",&n,&m))    {        g.init();        while(m--)        {            scanf("%d%d%d",&a,&b,&l);            g.add(a,b,l);        }        spfa(1);        double ans=0;        for(int i=1; i<=n; ++i)            ans+=dist[i];        printf("%.2f\n",ans/(n-1));    }}

  另外说一句,设V是点数,E是边数;标准的dijkstra算法,使用邻接矩阵,复杂度是O(V^2)的,使用堆优化或者优先队列优化,再使用恰当的建图方式,可以有效降低复杂度。标准spfa算法,使用邻接表,复杂度大概是O(k*E),k根据图的不同,可能很小,也可能大到接近V,用某些优化方法可以使k减小。换言之,应该根据图的特性(稠密图?稀疏图?)来选择最合适的算法。

  D. 迷宫里有两只薛定谔的猫!

  这道题是求任意两点之间最短距离的期望。或许因为和上一题太像,许多人选择在每一个点上都跑一次dijkstra算法。方法上没有问题,但为什么不使用floyd算法呢……在多源最短路的问题上,floyd算法显然代码更短,效率更高。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN=205;const int INF=0x3f3f3f3f;int g[MAXN][MAXN],n;void floyd(){    for(int k=1; k<=n; ++k)        for(int i=1; i<=n; ++i)            for(int j=1; j<=n; ++j)                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);}int main(){    int m,a,b,l;    while(~scanf("%d%d",&n,&m))    {        memset(g,0x3f,sizeof(g));        for(int i=1; i<=n; ++i)            g[i][i]=0;        while(m--)        {            scanf("%d%d%d",&a,&b,&l);            if(l<g[a][b])                g[a][b]=g[b][a]=l;        }        floyd();        double ans=0;        for(int i=1; i<=n; ++i)            for(int j=1; j<=n; ++j)                ans+=g[i][j];        printf("%.2f\n",ans/(n*(n-1)));    }}

  E. 迷宫里有一只深井冰!

  这道题,大概是渣诚满满的恶意……这是一道基础的TSP旅行商问题,是一个NP问题,这也意味着它并没有多项式复杂度的解法,所以不属于各种最短路算法的范畴;从至多10个点的数据量大概也可以看出,我们只是想让大家写一发暴搜。

  不过暴力也是有很多方法的,我的做法相当于取了个巧;假设从0点出发,用next_permutation()函数穷举出所有以0开头的排列,然后计算这种排列代表的走法的路径长度,取最短的即可。这样暴力的复杂度是O(n!)。

  另外,这道题是可以状压dp的,可以把复杂度降到O(n^2*2^n),有兴趣的童鞋可以研究一下。简单地说,设从0点出发,dp(i,j)表示在状态i下访问到j点的最短路径长度,则dp((1<<n)-1,k)就表示遍历过所有点且以k点为终点的最短路径长度。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int INF=0x3f3f3f3f;int g[10][10],s[10];int main(){    int n;    while(~scanf("%d",&n))    {        for(int i=0; i<n; ++i)            for(int j=0; j<n; ++j)                scanf("%d",&g[i][j]);        for(int i=0; i<n; ++i)            s[i]=i;        int ans=INF;        while(s[0]==0)        {            int tmp=g[s[n-1]][s[0]];            for(int i=1; i<n; ++i)                tmp+=g[s[i-1]][s[i]];            ans=min(ans,tmp);            next_permutation(s,s+n);        }        printf("%d\n",ans);    }}
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int INF=0x3f3f3f3f;int g[10][10],dp[10][1<<10];int main(){    int n;    while(~scanf("%d",&n))    {        for(int i=0; i<n; ++i)            for(int j=0; j<n; ++j)                scanf("%d",&g[i][j]);        memset(dp,0x3f,sizeof(dp));        dp[0][1]=0;        for(int i=1; i<(1<<n); i+=2)            for(int j=0; j<n; ++j)                if(i&(1<<j))                    for(int k=0; k<n; ++k)                        if(!(i&(1<<k)))                            dp[k][i|(1<<k)]=min(dp[k][i|(1<<k)],dp[j][i]+g[j][k]);        int ans=INF;        for(int i=1; i<n; ++i)            ans=min(ans,dp[i][(1<<n)-1]+g[i][0]);        printf("%d\n",ans);    }}

  F. 结点距离下集

  这是一道陈题,也是我觉得出题有失误的一道题。倒不是因为这道题所需知识超出了范围,而是这道题的题目描述相当不清晰。题目实际是在描述这样一个图,首先这个图是一个森林(对,没有环),在森林中的每棵树都有这样的特点,就是其中有一个节点是根节点(即题目中的集换中心),而其它节点的度(双向边,无所谓入度出度)小于等于2,形象一点地说,就是每一棵树都像神经元一样,根节点是细胞体,其余节点都在各条突触上,且每条突触不会分叉。

  所以,在当前的知识储备下,做法就是先遍历整个森林,找到每棵树上的根节点(统计度数就可以了,如果所有节点的度数都小于等于2,则这棵树必定是一条链,可以任选一个节点作为根节点);然后,再从根节点出发,对每棵树进行遍历,并记录每个节点的深度,和所在链的编号。这样,任意一棵树中的两个节点,倘若在一条链上,其最小距离就是深度的差,倘若不在一条链上,其距离就是深度的和(因为要通过根节点)。放一下渣诚的标程作为参考。

//trashLHC#include<iostream>#include<cstdio>#include<cmath>#include<cstdlib>#define MAXV 100001int a[MAXV],b[MAXV];typedef struct ANode{   int adjvex;   struct ANode *nextarc;   int info;   int router;}ArcNode;typedef struct Vnode{        int data;        ArcNode *firstarc;}VNode;typedef VNode AdjList[MAXV];typedef struct{   AdjList adjlist;   int n,e;}ALGraph;int visited[MAXV];void CreateList(ALGraph *&G,int n,int l){   int i,j;   ArcNode *p,*q;   G=(ALGraph *)malloc(sizeof(ALGraph));   for(i=0;i<=n;i++)      G->adjlist[i].firstarc=NULL;   for(i=0;i<l;i++)   {            p=(ArcNode *)malloc(sizeof(ArcNode));            p->adjvex=b[i];            p->nextarc=G->adjlist[a[i]].firstarc;            G->adjlist[a[i]].firstarc=p;            q=(ArcNode *)malloc(sizeof(ArcNode));            q->adjvex=a[i];            q->nextarc=G->adjlist[b[i]].firstarc;            G->adjlist[b[i]].firstarc=q;   }   G->n=n;G->e=l;}int route=0;int length=0;int distance[MAXV];int rout[MAXV];void DFS(ALGraph *G,int v){   ArcNode *p,*q;   visited[v]=1;   p=G->adjlist[v].firstarc;   p->router=route;   p->info=length;   distance[v]=p->info;   rout[v]=p->router;   while(p!=NULL)   {      if(!visited[p->adjvex])         break;      p=p->nextarc;   }   if(p==NULL)   {     route++;     length=0;   }   p=G->adjlist[v].firstarc;   while(p!=NULL)   {      if(!visited[p->adjvex])      {         length++;         DFS(G,p->adjvex);      }      p=p->nextarc;   }}void DetroyGraph(ALGraph *&G){    ArcNode *p,*q;    for(int i=0;i<=G->n;i++)    {       p=G->adjlist[i].firstarc;       while(p!=NULL)       {           q=p;           p=p->nextarc;           free(q);       }    }    free(G);}int main(){    int t;    scanf("%d",&t);    while(t--)    {       int m,n;       scanf("%d%d",&m,&n);       ALGraph *G2;       int max=0;       int counter[MAXV];       for(int i=0;i<=m;i++)       {           counter[i]=0;           distance[i]=0;           rout[i]=0;           visited[i]=0;       }       for(int i=0;i<m;i++)       {          scanf("%d%d",&a[i],&b[i]);          counter[a[i]]++;counter[b[i]]++;       }       int recorder=0;       for(int i=0;i<=m;i++)          if(max<counter[i])          {                    recorder=i;                    max=counter[i];          }       CreateList(G2,m,m);       DFS(G2,recorder);      for(int i=0;i<n;i++)      {         int temp1,temp2;         scanf("%d%d",&temp1,&temp2);         if(rout[temp1]==rout[temp2])printf("%d\n",abs(distance[temp1]-distance[temp2]));         else                       printf("%d\n",distance[temp1]+distance[temp2]);      }       route=0;       length=0;      DetroyGraph(G2);    }}

  而我不是这么做的,我的做法超出了课程范围,不过适用范围会更广一些,只要保证图是一个森林就可以了,对每棵树的具体形状并没有要求,这里简单说一下供有兴趣的童鞋探究。我的做法是处理出树上任意两点的lca,即最近公共祖先,则这两点的距离就是这两点的深度之和减去2×最近公共祖先的深度。处理lca的方法有两种,一种是在线倍增法,复杂度是O(nlogn),一种是离线Tarjan算法,复杂度上O(n)。下面分别放出两种做法的标程。

//by wjfwzzc//lca+倍增#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN=100005;const int MAXM=200005;const int maxd=17;struct graph{    int head[MAXN];    int to[MAXM];    int next[MAXM];    int tot;    void init()    {        tot=0;        memset(head,0xff,sizeof(head));    }    void add(int u,int v)    {        to[tot]=v;        next[tot]=head[u];        head[u]=tot++;    }} g;int d[MAXN],f[MAXN][maxd];void dfs(int u,int fa){    f[u][0]=fa;    for(int i=1; i<maxd; ++i)        f[u][i]=f[f[u][i-1]][i-1];    for(int i=g.head[u]; ~i; i=g.next[i])    {        int v=g.to[i];        if(v!=fa)        {            d[v]=d[u]+1;            dfs(v,u);        }    }}int lca(int u,int v){    if(d[u]<d[v])        swap(u,v);    int k=d[u]-d[v];    for(int i=0; i<maxd; ++i)        if((1<<i)&k)            u=f[u][i];    if(u==v)        return u;    for(int i=maxd-1; i>=0; --i)        if(f[u][i]!=f[v][i])        {            u=f[u][i];            v=f[v][i];        }    return f[u][0];}int main(){    int t,m,n,a,b;    scanf("%d",&t);    while(t--)    {        g.init();        scanf("%d%d",&m,&n);        while(m--)        {            scanf("%d%d",&a,&b);            g.add(a,b);            g.add(b,a);        }        d[0]=0;        dfs(0,-1);        while(n--)        {            scanf("%d%d",&a,&b);            printf("%d\n",d[a]+d[b]-2*d[lca(a,b)]);        }    }}
//by wjfwzzc//lca+Tarjan#include<cstdio>#include<cstring>using namespace std;const int MAXN=100005;const int MAXM=200005;struct graph{    int head[MAXN];    int to[MAXM];    int idx[MAXM];    int next[MAXM];    int tot;    void init()    {        tot=0;        memset(head,0xff,sizeof(head));    }    void add(int u,int v,int w=-1)    {        to[tot]=v;        idx[tot]=w;        next[tot]=head[u];        head[u]=tot++;    }} g,q;int x[MAXN],y[MAXN],z[MAXN];int f[MAXN],d[MAXN];bool vis[MAXN];int find(int x){    if(f[x]!=x)        return f[x]=find(f[x]);    return f[x];}void tarjan(int u){    vis[u]=true;    f[u]=u;    for(int i=q.head[u]; ~i; i=q.next[i])    {        int v=q.to[i];        if(vis[v])            z[q.idx[i]]=find(v);    }    for(int i=g.head[u]; ~i; i=g.next[i])    {        int v=g.to[i];        if(!vis[v])        {            d[v]=d[u]+1;            tarjan(v);            f[v]=u;        }    }}int main(){    int t,m,n,a,b;    scanf("%d",&t);    while(t--)    {        g.init();        scanf("%d%d",&m,&n);        while(m--)        {            scanf("%d%d",&a,&b);            g.add(a,b);            g.add(b,a);        }        q.init();        for(int i=1; i<=n; ++i)        {            scanf("%d%d",&x[i],&y[i]);            q.add(x[i],y[i],i);            q.add(y[i],x[i],i);        }        memset(vis,false,sizeof(vis));        d[0]=0;        tarjan(0);        for(int i=1; i<=n; ++i)            printf("%d\n",d[x[i]]+d[y[i]]-2*d[z[i]]);    }}

  G. Fibonacci Tree

  这是我在2013年ACM成都现场赛做过的原题,网上自然有很多题解;但有十余位童鞋选择直接照搬网上代码还是很令人寒心。这道题是说权值只有1和0两种的一个图,问其是否存在权值和为Fibonacci数的生成树。直接做一遍最小生成树和最大生成树(最小生成树的两种算法本质都是贪心,所以可以很容易地改写出求最大生成树),然后注意到,我们假设把得到的最小生成树逐边修改得到最大生成树,在修改的过程中,必然会出现把0边改成1边这样的情况,换言之,最小生成树和最大生成树的权值之间形成的区间,一定是致密的,再换种说法,这个区间内任何一个值对应的生成树必然存在;所以,我们只要判断是否有Fibonacci数存在于这个区间内就可以了。所以最后的做法是预处理出一些Fibonacci数(30个左右即可),然后在求出最小生成树和最大生成树之后,穷举Fibonacci数进行判断即可。这里给出使用kruskal和prim两种算法的标程。

#include<cstdio>#include<algorithm>using namespace std;const int MAXN=1005;const int MAXM=100005;struct edge{    int u,v,w;    edge(int _u=0,int _v=0,int _w=0):u(_u),v(_v),w(_w) {}} e[MAXM];bool cmp1(const edge &a,const edge &b){    return a.w<b.w;}bool cmp2(const edge &a,const edge &b){    return a.w>b.w;}bool (*cmp[])(const edge&,const edge&)= {cmp1,cmp2};int n,m;int u[MAXN];void init(){    for(int i=1; i<=n; ++i)        u[i]=i;}int find(int x){    if(u[x]!=x)        u[x]=find(u[x]);    return u[x];}void merge(int x,int y){    u[find(x)]=find(y);}int kruskal(int k){    sort(e,e+m,cmp[k]);    int ret=0,cnt=0;    init();    for(int i=0; cnt<n-1&&i<m; ++i)        if(find(e[i].u)!=find(e[i].v))        {            merge(e[i].u,e[i].v);            ret+=e[i].w;            ++cnt;        }    return cnt<n-1?-1:ret;}int main(){    int f[30];    f[0]=1;    f[1]=2;    for(int i=2; i<30; ++i)        f[i]=f[i-1]+f[i-2];    while(~scanf("%d%d",&n,&m))    {        for(int i=0; i<m; ++i)            scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);        int amin=kruskal(0),amax=kruskal(1);        bool flag=false;        if(~amin&&~amax)            for(int i=0; !flag&&i<30; ++i)                if(amin<=f[i]&&amax>=f[i])                    flag=true;        puts(flag?"Yes":"No");    }}
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN=1005;const int INF=0x3f3f3f3f;bool lvis[MAXN],hvis[MAXN];int g[MAXN][MAXN][2],n,lc[MAXN],hc[MAXN],amin,amax;void prim(){    memset(lvis,false,sizeof(lvis));    memset(hvis,false,sizeof(hvis));    for(int i=1; i<=n; ++i)    {        lc[i]=g[1][i][0];        hc[i]=g[1][i][1];    }    lvis[1]=hvis[1]=true;    amin=amax=0;    for(int i=1; i<n; ++i)    {        int lmark=-1,hmark=-1,lminc=INF,hminc=-1;        for(int j=1; j<=n; ++j)        {            if(!lvis[j]&&lminc>lc[j])            {                lminc=lc[j];                lmark=j;            }            if(!hvis[j]&&hminc<hc[j])            {                hminc=hc[j];                hmark=j;            }        }        if(!~lmark)        {            amin=-1;            break;        }        if(!~hmark)        {            amax=-1;            break;        }        lvis[lmark]=hvis[hmark]=true;        amin+=lc[lmark];        amax+=hc[hmark];        for(int j=1; j<=n; ++j)        {            if(!lvis[j]&&lc[j]>g[lmark][j][0])                lc[j]=g[lmark][j][0];            if(!hvis[j]&&hc[j]<g[hmark][j][1])                hc[j]=g[hmark][j][1];        }    }}int main(){    int f[30];    f[0]=1;    f[1]=2;    for(int i=2; i<30; ++i)        f[i]=f[i-1]+f[i-2];    int m,a,b,c;    while(~scanf("%d%d",&n,&m))    {        for(int i=1; i<=n; ++i)        {            for(int j=1; j<=n; ++j)            {                g[i][j][0]=INF;                g[i][j][1]=-1;            }            g[i][i][0]=g[i][i][1]=0;        }        while(m--)        {            scanf("%d%d%d",&a,&b,&c);            if(c<g[a][b][0])                g[a][b][0]=g[b][a][0]=c;            if(c>g[a][b][1])                g[a][b][1]=g[b][a][1]=c;        }        prim();        bool flag=false;        if(~amin&&~amax)            for(int i=0; !flag&&i<30; ++i)                if(amin<=f[i]&&amax>=f[i])                    flag=true;        puts(flag?"Yes":"No");    }}

  最后我想说,上机愈少,期末临近,且练且珍惜。

0 0
原创粉丝点击