第四次练习赛解题报告及标程

本次练习赛涵盖了回文串判断、迷宫路径、字符串处理、单调栈与单调队列操作以及组合数求模等题目。解题涉及DFS、BFS、递归优化、记忆化搜索、快速幂等算法,为参赛者提供了丰富的算法实践。

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

  第四次练习赛本来想有点创新,结果还是很不幸地做得不够好。

  A. AString.h(I)

  判断并输出所有是回文串的字符串,唯一要注意的仅仅是分割字符串。数据量其实很小。

//by trashLHC

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<fstream>
#define MAX 101
using namespace std;

bool isSymmetric(string s){
     cout<<s<<endl;
    for(int i=0;i<s.length()/2;i++)
       if(s[i]!=s[s.length()-i-1]&&s[i]!=s[s.length()-i-1]-32&&s[i]!=s[s.length()-i-1]+32)
           return false;
    return true;
}

string solve(string s){
    int place[MAX],top=0;
    place[top++]=-1;
    for(int i=0;i<s.length();i++)
        if(s[i]==' ')
           place[top++]=i;
    place[top++]=s.length();

    for(int i=top-1;i>0;i--){
        if(!isSymmetric(s.substr(place[i-1]+1,place[i]-place[i-1]-1))){
             s.erase(place[i-1]+1,place[i]-place[i-1]);
        }
    }
    return s;
}

int main(){
    //ifstream infile("in.txt",ios::in);
    //ofstream outfile("out.txt",ios::out);
    int n;
    infile>>n;
    infile.get();
    while(n--){
        string s;
        getline(infile,s);
        outfile<<solve(s)<<endl;
    }
}
//by wjfwzzc

#include<cstdio>
#include<cstring>
#include<cctype>
using namespace std;
char str[105];
bool isPalin(char *s)
{
    int l=strlen(s);
    for(int i=0; i<(l>>1); ++i)
        if(tolower(s[i])!=tolower(s[l-i-1]))
            return false;
    return true;
}
int main()
{
    int n;
    scanf("%d",&n);
    getchar();
    while(n--)
    {
        gets(str);
        char *sub=strtok(str," ");
        while(sub)
        {
            if(isPalin(sub))
                printf("%s ",sub);
            sub=strtok(NULL," ");
        }
        putchar('\n');
    }
}

  B. AString.h(II)

  这道题更简单了,匹配字符串并删除,数据量同样很小。用string类自带函数就可以轻易解决。

#include<iostream>
#include<string>
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    string s,t;
    while(cin>>s)
    {
        cin.get();
        getline(cin,t);
        int k=t.find(s);
        for(int i=t.find(s); i!=t.npos; i=t.find(s))
            t.erase(i,s.size());
        cout<<t<<endl;
    }
}

  C. MH370的黑匣子

  就算它题干变得再花,也不过就是个迷宫。只判断是否存在路径,不要求路径最短,所以dfs即可;bfs也可以不过稍微难写一点点。

  以下分别放出两个版本,先是dfs的。

#include<cstdio>
#include<cstring>
using namespace std;
const int dx[]= {-1,0,1,0},dy[]= {0,1,0,-1};
char g[15][15];
bool dfs(int x,int y)
{
    g[x][y]='#';
    for(int i=0; i<4; ++i)
    {
        int tx=x+dx[i],ty=y+dy[i];
        if(g[tx][ty]=='B'||(g[tx][ty]=='*'&&dfs(tx,ty)))
            return true;
    }
    return false;
}
int main()
{
    int n,m,sx,sy;
    while(~scanf("%d%d",&n,&m))
    {
        memset(g,'#',sizeof(g));
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
            {
                scanf(" %c",&g[i][j]);
                if(g[i][j]=='S')
                {
                    sx=i;
                    sy=j;
                }
            }
        puts(dfs(sx,sy)?"Yes":"No");
    }
}

  然后是bfs的。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int dx[]= {-1,0,1,0},dy[]= {0,1,0,-1};
char g[15][15];
int sx,sy;
bool bfs()
{
    queue<pair<int,int> > q;
    q.push(make_pair(sx,sy));
    while(!q.empty())
    {
        pair<int,int> tmp=q.front();
        q.pop();
        for(int i=0; i<4; ++i)
        {
            int tx=tmp.first+dx[i],ty=tmp.second+dy[i];
            if(g[tx][ty]=='B')
                return true;
            else if(g[tx][ty]=='*')
            {
                q.push(make_pair(tx,ty));
                g[tx][ty]='#';
            }
        }
    }
    return false;
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        memset(g,'#',sizeof(g));
        for(int i=1; i<=n; ++i)
            for(int j=1; j<=m; ++j)
            {
                scanf(" %c",&g[i][j]);
                if(g[i][j]=='S')
                {
                    sx=i;
                    sy=j;
                }
            }
        puts(bfs()?"Yes":"No");
    }
}

  D. 传话游戏

  那些个吐槽题干描述不清晰的童鞋们,这道题可是微软2013编程之美资格赛第1题原题……照例注意字符串的分割,然后模拟一下替换过程就好了。标程用了map来记录替换字典,也可以写个pair或者struct。

#include<cstdio>
#include<cstring>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
    int t,n,m;
    char a[22],b[22],str[105];
    scanf("%d",&t);
    for(int cas=1; cas<=t; ++cas)
    {
        scanf("%d%d",&n,&m);
        map<string,string> dic;
        vector<string> stc;
        vector<string>::iterator it;
        while(m--)
        {
            scanf("%s%s",a,b);
            dic[a]=b;
        }
        getchar();
        gets(str);
        char *sub=strtok(str," ");
        while(sub)
        {
            stc.push_back(sub);
            sub=strtok(NULL," ");
        }
        while(--n)
            for(it=stc.begin(); it!=stc.end(); ++it)
                if(dic[*it]!="")
                    *it=dic[*it];
        printf("Case #%d:\n",cas);
        for(it=stc.begin(); it!=stc.end(); ++it)
            printf("%s ",(*it).c_str());
        putchar('\n');
    }
}

  E. Monotonic Stack

  接下来的三道题都属于开阔视野的类型。建议想要尝试ACM竞赛的童鞋好好研究一下。

  这道题模拟了单调栈的基本操作,同时在hint里给出了单调栈的入门练习题(POJ2559)。因为介绍它的文章和题解一大堆,这里不多解释。

#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int MAXN=100005;
int MonotonicStack[MAXN],top;
int main()
{
    int m,e;
    char op[10];
    while(~scanf("%d",&m))
    {
        top=-1;
        while(m--)
        {
            scanf("%s",op);
            switch(op[1])
            {
            case 'U':
                scanf("%d",&e);
                while(top!=-1&&MonotonicStack[top]>=e)
                    --top;
                MonotonicStack[++top]=e;
                break;
            case 'O':
                if(top!=-1)
                    --top;
                break;
            case 'E':
                if(top==-1)
                    puts("EMPTY");
                else
                    printf("%d\n",MonotonicStack[top]);
                break;
            }
        }
    }
}
#include<cstdio>
#include<stack>
using namespace std;
stack<int> MonotonicStack;
int main()
{
    int m,e;
    char op[10];
    while(~scanf("%d",&m))
    {
        while(m--)
        {
            scanf("%s",op);
            switch(op[1])
            {
            case 'U':
                scanf("%d",&e);
                while(!MonotonicStack.empty()&&MonotonicStack.top()>=e)
                    MonotonicStack.pop();
                MonotonicStack.push(e);
                break;
            case 'O':
                if(!MonotonicStack.empty())
                    MonotonicStack.pop();
                break;
            case 'E':
                if(MonotonicStack.empty())
                    puts("EMPTY");
                else
                    printf("%d\n",MonotonicStack.top());
                break;
            }
        }
        while(!MonotonicStack.empty())
            MonotonicStack.pop();
    }
}

  F. Monotonic Queue

  这道题模拟了单调队列的基本操作,也给出了单调队列的入门练习题(POJ2823),和上面一样两道题都是很有趣的问题,而且相关题解众多,不多阐述。

#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int MAXN=1005;
int MonotonicQueue[MAXN],front,size;
int main()
{
    int n,m,e;
    char op[10];
    while(~scanf("%d%d",&n,&m))
    {
        front=size=0;
        while(m--)
        {
            scanf("%s",op);
            switch(op[1])
            {
            case 'U':
                scanf("%d",&e);
                while(size>0&&MonotonicQueue[(front+size-1)%n]>=e)
                    --size;
                if(size==n)
                {
                    front=(front+1)%n;
                    --size;
                }
                MonotonicQueue[(front+(size++))%n]=e;
                break;
            case 'O':
                if(size>0)
                {
                    front=(front+1)%n;
                    --size;
                }
                break;
            case 'E':
                if(size==0)
                    puts("EMPTY");
                else
                    printf("%d\n",MonotonicQueue[front]);
                break;
            }
        }
    }
}
#include<cstdio>
#include<deque>
using namespace std;
deque<int> MonotonicQueue;
int main()
{
    int n,m,e;
    char op[10];
    while(~scanf("%d%d",&n,&m))
    {
        while(m--)
        {
            scanf("%s",op);
            switch(op[1])
            {
            case 'U':
                scanf("%d",&e);
                while(!MonotonicQueue.empty()&&MonotonicQueue.back()>=e)
                    MonotonicQueue.pop_back();
                if(MonotonicQueue.size()==n)
                    MonotonicQueue.pop_front();
                MonotonicQueue.push_back(e);
                break;
            case 'O':
                if(!MonotonicQueue.empty())
                    MonotonicQueue.pop_front();
                break;
            case 'E':
                if(MonotonicQueue.empty())
                    puts("EMPTY");
                else
                    printf("%d\n",MonotonicQueue.front());
                break;
            }
        }
        MonotonicQueue.clear();
    }
}

  G. Jeffrey的组合数

  这道题就是最普通的组合数求模。我非常欣喜地看到童鞋们给出了相当多种不同的做法;虽然因为数据组数比较小,以至于有些以为会TLE的代码也过了,但依然为其中的想法感到很高兴。限于时间原因,这里给出的一些做法不能涵盖所有做法。

  首先是能拿到0.5分(1≤m≤30,0≤n≤m)的做法,最普通的递归。每次查询复杂度是指数级的。

#include<cstdio>
using namespace std;
const int INF=1000000007;
long long C(int m,int n)
{
    if(n==0||m==n)
        return 1;
    return (C(m-1,n)+C(m-1,n-1))%INF;
}
int main()
{
    int m,n;
    while(~scanf("%d%d",&m,&n))
        printf("%lld\n",C(m,n));
}

  其次是能拿到0.8分(1≤m≤10^3,0≤n≤m)的做法,即把递归的中间过程用数组记录下来(记忆化搜索),或者干脆直接dp。复杂度上,预处理O(m×n),查询O(1)。

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1005;
const int INF=1000000007;
long long C[MAXN][MAXN];
void init()
{
    C[0][0]=1;
    for(int i=1; i<MAXN; ++i)
    {
        C[i][0]=1;
        for(int j=1; j<=min(i,MAXN-1); ++j)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%INF;
    }
}
int main()
{
    init();
    int m,n;
    while(~scanf("%d%d",&m,&n))
        printf("%lld\n",C[m][n]);
}

  接下来是能拿到1分满分(1≤m≤5×10^5,0≤n≤m)的做法。

  李晨豪的做法。他首先通过Euler筛法线性处理出素数表,然后将每次询问进行质因数分解,快速幂再求和就可以了;质因数分解的方法是,对这个组合数的三个阶乘,分别筛出每个阶乘的质因数个数。预处理复杂度O(m),查询复杂度不太好分析,上界大概是O(m)(也可能是O(mlogm),我不确定);基本上是因为数据组数小(10组左右)水过去的。

//by 李晨豪

#include <cstdio>
#include <iostream>
using namespace std;
const long long MAXN = 1e9 + 7;
int n,m;
long long ans = 0;
int numx = 0;
long long f[60100];
bool prime[501000];

void pri()
{
    for (int i = 2; i <= 501000; i++)
    {
        if (prime[i] == 0)
            f[++numx] = i;
        for (int j = 1; j <= numx && i * f[j] <= 500000; j++)
        {
            prime[i * f[j]] = 1;
            if (i % f[j] == 0) break;
        }
    }

}
void calc(long long & x, long long a,int b)
{
    while(b)
    {
        if(b&1)
        {
            x = (x * a) % MAXN;
        }
        a = (a * a) % MAXN;
        b >>= 1;
    }
}
int ca(long long x,long long p)
{
    long long aans = 0;
    long long rec = p;
    while(x>=rec)
    {
        aans += x/rec;
        rec *= p;
    }
    return aans;
}
int main()
{
    pri();
    while (~scanf("%d %d",&n,&m))
    {
        ans = 1;
        for (int i = 1; i <= numx; i++)
        {
            int tmp = ca((long long)n,f[i]) - ca((long long)m,f[i])-ca((long long)(n-m),f[i]);
            calc(ans,f[i],tmp);
        }
        printf("%lld\n",ans);
    }
}

  户建坤的做法。首先算出n!求模的结果,以及(m-n+1)乘到m并求模的结果,然后因为求模的存在,不能直接用后者除以前者,所以用扩展gcd拿出前者关于模的乘法逆元,然后两者相乘即可。查询复杂度O(n),其中阶乘是O(n),扩展gcd是O(logn);因为数据组数(查询次数)小,这是目前代码中跑得最快的。

//by 户建坤

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctype.h>
#include <cstdlib>
#include <algorithm>

using namespace std;

const long long maxn = 1000000007;

long long extgcd(long long a,long long b,long long&x,long long&y)
{
    long long d,t;
    if(b==0)
    {
        x = 1;
        y = 0;
        return a;
    }
    d = extgcd(b,a%b,x,y);
    t = x;
    x = y;
    y = t-a/b*y;
    return d;
}
long long get_mod(long long a, long long b)
{
    long long ans = 1;
    for(long long x = a;x <= b;x++)
    {
        ans = (x*ans)%maxn;
    }
    return ans;
}
long long m, n;
long long mm, nn;
long long x, y;
int main()
{
    while(scanf("%lld %lld", &m, &n) != EOF)
    {
        mm = get_mod(m - n + 1, m);
        nn = get_mod(1, n);
        extgcd(nn,maxn,x,y);
        x*=mm;
        x = x%maxn;
        if(x<0)
          x+=maxn;
        printf("%lld\n", x);
    }
    return 0;
}

  何玥的做法。Lucas定理,专门用于解决组合数求模问题,虽然我知道这个定理但是不会用也不打算用……证明比较复杂,可以在网上搜一下。复杂度同样不好分析,大概每次查询O(mlogm)的样子,我不是很清楚。

//by 何玥

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long lld;
lld  n, m, p;

lld Ext_gcd(lld a,lld b,lld &x,lld &y){
    if(b==0) { x=1, y=0; return a; }
    lld ret= Ext_gcd(b,a%b,y,x);
    y-= a/b*x;
    return ret;
}
lld Inv(lld a,int m){
    lld d,x,y,t= (lld)m;
    d= Ext_gcd(a,t,x,y);
    if(d==1) return (x%t+t)%t;
    return -1;
}

lld Cm(lld n, lld m, lld p)
{
    lld a=1, b=1;
    if(m>n) return 0;
    while(m)
    {
        a=(a*n)%p;
        b=(b*m)%p;
        m--;
        n--;
    }
    return (lld)a*Inv(b,p)%p;
}

int Lucas(lld n, lld m, lld p)
{
    if(m==0) return 1;
    return (lld)Cm(n%p,m%p,p)*(lld)Lucas(n/p,m/p,p)%p;
}

int main()
{

    while(scanf("%lld%lld",&n,&m)!=EOF)
    {
        printf("%d\n",Lucas(n,m,1000000007));
    }
    return 0;
}

  最后是我的做法……线性预处理出阶乘以及阶乘关于模的逆元,然后相乘即可;其中线性预处理逆元感谢昂神指点(我以前的板子是利用费马小定理做快速幂,复杂度是O(mlogm)的)。预处理复杂度O(m),查询复杂度O(1),是满分做法中理论复杂度最优且代码最短的……(身为ACMer必然是各种板子都写过了Orz)

//by wjfwzzc

#include<cstdio>
using namespace std;
const int MAXN=500005;
const int INF=1000000007;
long long fac[MAXN],invfac[MAXN];
void init()
{
    fac[0]=fac[1]=invfac[0]=invfac[1]=1;
    for(int i=2; i<MAXN; ++i)
    {
        fac[i]=fac[i-1]*i%INF;
        invfac[i]=(INF-INF/i)*invfac[INF%i]%INF;
    }
    for(int i=2; i<MAXN; ++i)
        invfac[i]=invfac[i-1]*invfac[i]%INF;
}
inline long long C(int m,int n)
{
    if(m<0||n<0||m<n)
        return 0;
    return fac[m]*invfac[n]%INF*invfac[m-n]%INF;
}
int main()
{
    init();
    int m,n;
    while(~scanf("%d%d",&m,&n))
        printf("%lld\n",C(m,n));
}

  以上就是全部题解了。如果做练习赛的人能够再多一些就好了……(大概我又想多了?)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值