美文网首页
洛谷2454 [SDOI2006]数字串位置

洛谷2454 [SDOI2006]数字串位置

作者: tysnd | 来源:发表于2019-04-26 19:42 被阅读0次

    这是一道山东省选的题目。听大佬说,山东省选是最难的省选。所以这题是和lpjworkroom和云中翻月两位大佬合作完成。
    lpjworkroom https://www.jianshu.com/u/5b56e393a2eb
    云中翻月 https://www.jianshu.com/u/0269cab93a42
    首先,在将做法之前,先吐槽一下,这题是我目前做的最恶心的题目!。理由如下:
    1.乍一看是个普通的字符串匹配问题,但是母串是无限长的字符串,而且字串的长度最大为200,所以,普通的字符串匹配是解决不了问题的。
    2.即使求出来了字串开头的数字,但要求它所在的位置,还要用高精度,因此不管是思路上还是编码上,都是很不容易的。
    3.最糟糕的是,这一题目前只找到两个oj上可以提交,vijos和洛谷,
    https://vijos.org/p/1005
    https://www.luogu.org/problemnew/show/P2454
    而且这两个网站上的数据都有错!因为在vijos上找了一篇题解代码,用这篇代码在vijos上和洛谷上提交,都能AC,但是我们找到了反例输入,用题解代码运行答案显然错误。另外,网上也根本找不到题解,所以,我要写一下我们的思路。

    方法一:滚动数组dp,找最长公共字串
    结果:TLE,但保证正确。
    这个是我自己的做法,做法很简单,只要会写最长公共字串,就会这样写。因为模式串最长只有200,所以只需开2*200的数组,第一维滚动母串的位置,循环1到无穷(不过显然不会到无穷),更新这个数字的代表的母串的位置的dp值,当公共字串的长度等于模式串的长度时输出当前位置即可。
    代码如下:

    //TLE
    #include<iostream>
    #include<string>
    #include<algorithm>
    using namespace std;
    string s="#";
    int curnum,curpos;
    int dp[2][205];
    string myto_string(int);
    int main()
    {
        ios::sync_with_stdio(false);
        string temp;
        cin>>temp;
        s+=temp;
        curnum=curpos=1;
        for(int i=1;;i++)
        {
            string temps=myto_string(curnum);
            char c=temps[curpos-1];
    //      cout<<c;
            for(int j=1;j<s.size();j++)
            {
    //          int x=i&1;
                if(c==s[j])
                    dp[i&1][j]=dp[(i-1)&1][j-1]+1;
                else
                    dp[i&1][j]=0;
            }
            curpos++;
            if(curpos>temps.size())
            {
                curpos=1;
                curnum++;
            }
            if(dp[i&1][s.size()-1]==s.size()-1)
            {
                cout<<i-s.size()+2<<endl;
                return 0;
            }
        }
    //  cout<<endl;
        return 0;
    }
    string myto_string(int n)
    {
        string res="";
        while(n)
        {
            res+=n%10+'0';
            n/=10;
        }
        reverse(res.begin(),res.end());
        return res;
    }
    

    方法二:分情况讨论,高精度求解
    根据方法一的失败经验,我们很容易发现:在求解过程中,不能涉及对无穷长的母串的操作。而模式串的长度最大为200,所以很容易想到要对模式串进行操作。
    首先,我们明确,模式串是由一个或多个连续的数字组成的。那么,我们怎么求模式串出现在母串里的位置呢?问题比较复杂,我们就先对问题进行分解:
    1.求出模式串的开头数字,并求出开头数字前面被截掉了几位
    2.高精度求最后答案

    我们先做出几个规定:
    (1)final:组成模式串的一系列连续数字的开头数字
    (2)firlack:final前面被截掉的位数
    然后,我们对这两个问题分别讨论。
    1.
    显然,我们只要能求出模式串是由哪个或哪一串尽量小的连续数字组成,就可以求出模式串在母串中的位置。我们假设模式串开始的数字为final。那么,我们就要对模式串进行划分来求解final。通俗的说,就是对模式串进行切割。而切割共有三种情况:
    a.模式串是一个完整的数字
    对于这种情况,如果模式串甚至不能表示一个完整的数字,假设记为fakefinal,那么显然fakefinal>final,其第一次出现的位置也在final后。所以模式串至少是一个完整的数字。
    注意:
    这种情况仅有一种反例,那就是模式串全0时。显然,这时候在这串0前面补上一个1作为final是最优情况。由于仅有一种特殊情况,特判即可。
    b.模式串是多个(3个及以上)连续的数字
    对于这种情况,我们需要枚举final在模式串里结束的位置和每个数字的长度,这样不断判断下一个数字是否为上一个数字+1即可。对于所有可行情况,取最小值。
    注意:
    枚举模式串里连续数字长度时,要注意进位问题,即若模式串为89101112131,显然final=8。但是当我们枚举连续数字长度时,若仅以final的长度作为连续数字长度,就会发生错误。如本例中的10 11等数字就是2位。因此要在循环时随时更新连续数字长度。
    c.模式串是两个连续的数字
    对于这种情况,我们只需枚举切割位置,前一半记做first,后一半记做second,然后把first当做不完整的final,将其加一后与second比较,看是否second的后边与first+1的前面有公共部分,若有,则去掉一个公共串后拼接作为final(如23124,实际是123 124,first=23,second=124,first+1=24,与second有公共部分,则拼接后final=124)若没有公共部分,则直接拼接(如231,实际为123 124 ,first=23,second=1,first+1=24,没有公共部分,则final=124)。取所有可能结果的最小值作为final。
    综上三种情况,取最小值即为final。注意随时更新firlack。第1步结束。
    2.
    高精度求第一次出现的位置。推公式,难度不大,记得firlack。此处不加赘述。
    代码如下:

    #include<iostream>
    #include<fstream>
    #include<sstream>
    #include<stdlib.h>
    #include<string.h>
    #include<string>
    using namespace std;
    const int MAXL=200+50;
    const int base=10;
    class Bignum{
        public:
        int size,len; //len limits tostr()'s length
        int data[MAXL];
        friend ostream& operator<<(ostream& out,Bignum& a); 
        
        int& operator[](int x)
        {
            return data[x];
        }
        
        void add(Bignum b)
        {
            int maxsize=max(size,b.size);
            for(int i=0;i<maxsize;i++) data[i]+=b.data[i];
            for(int i=0;i<maxsize;i++){
                data[i+1]+=data[i]/base;
                data[i]%=base;
            }
            if (data[maxsize])  maxsize++;
            size=maxsize;
        }
        
        //assume this>b
        void minus(Bignum b)
        {
            for(int i=0;i<size;i++)
            {
                if(data[i]>=b[i]) data[i]-=b[i];
                else
                {
                    data[i]=data[i]+base-b[i];
                    data[i+1]--;    
                }
            }
            while(data[size-1]==0&&size) size--;
        }
        
        Bignum operator*(Bignum b)
        {
            Bignum c("0");
            int maxsize=size+b.size-1;
            for(int i=0;i<size;i++) for(int j=0;j<b.size;j++) c[i+j]=data[i]*b[j];
            for(int i=0;i<maxsize;i++){
                c[i+1]+=c[i]/base;
                c[i]%=base;
            }
            while(c[maxsize]){
                c[maxsize]+=c[maxsize-1]/base;
                c[maxsize-1]%=base;
                if(c[maxsize])  maxsize++;
                else    break;
            }
            c.size=maxsize;
            return c;
        }
        
        bool operator==(Bignum& b)
        {
            if(size!=b.size) return false;
            for(int i=0;i<size;i++) if(data[i]!=b[i]) return false;
            return true;
        }
        
        bool operator<(Bignum& b)
        {
            if(size!=b.size) return size<b.size;
            for(int i=size-1;i>=0;i--) if(data[i]!=b[i]) return data[i]<b[i];
            return false;
        }
        
        void operator=(Bignum b)
        {
            size=b.size;
            len=b.len;
            memset(data,0,sizeof(data));
            for(int i=0;i<size;i++) data[i]=b[i];
        }
        
        Bignum(string str)
        {
            size=len=str.size();
            memset(data,0,sizeof(data));
            for(int i=0;i<size;i++) data[size-i-1]=str[i]-'0';
        }
        
        Bignum()
        {
            size=len=0;
            memset(data,0,sizeof(data));
        }
        
        string tostr()
        {
            string s="";
            for(int i=len-1;i>=0;i--) s+=(data[i]+'0');
            return s;
        }
        
    };
    ostream& operator<<(ostream& out,Bignum& a)
    {
        for (int i=a.size-1;i>=0;i--)   out<<a.data[i];
        return out;
    }
    
    int N,firsta=0;
    string data;
    Bignum ans,final;
    
    string itoa(int a);
    bool all9(Bignum& a);
    
    int main()
    {
        
        //cin>>data;
        cin>>data;
        N=data.size();
        
        if (data==string(N,'0')){
            data="1"+data;
            N++;
            firsta=1;
        }
        final=Bignum(data);
        
        /*2*/
        /*i=0?*/
        for (int i=1;i<(N+1)/2;i++)
        {
            if (data[i]=='0')   continue;
            for (int j=i;j+i<N;j++)
            {
                Bignum fir=Bignum(data.substr(0,i)),sec=Bignum(data.substr(i,j));
                
                Bignum seclast=Bignum(data.substr(j,i));
                fir.add(Bignum("1"));
                if (!(fir.tostr()==seclast.tostr()))    continue;
                
                Bignum tmpfi=sec;
                tmpfi.minus(Bignum("1"));
                if (final<tmpfi)    continue;
                
                fir=sec;
                bool ok=true;
                fir.add(Bignum("1"));
                int k,nxtwid=fir.size;
                /**/
                for (k=j+i;k+nxtwid<N;k+=nxtwid)
                {
                    nxtwid=fir.size;
                    sec=Bignum(data.substr(k,nxtwid));
                    //cout<<"now ought to be:"<<fir<<" sec is:"<<sec<<endl;
                    if (!(fir==sec)){
                        ok=false;break;
                    }
                    fir.add(Bignum("1"));
                }
                if (!ok)    continue;
                
                //fir.add(Bignum("1"));
                for (int p=fir.size-1;k!=N;k++,p--)
                {
                    if (fir.data[p]!=(data[k]-'0')){
                        ok=false;break;
                    }
                }
                if (!ok)    continue;
                final=tmpfi;firsta=j-i;
            }
        }
        /*2 over*/
        /*3*/
        for (int i=1;i<=N-1;i++)
        {
            if (data[i]=='0')   continue;
            Bignum tt(data.substr(0,i));
            tt.add(Bignum("1"));
            string strfir=tt.tostr(),strsec=data.substr(i);
            int l;
            /*l: pre and suf 's public string*/
            for (l=min(i,N-i);l>0;l--)
            {
                string pre=strfir.substr(0,l),suf=strsec.substr(N-i-l);
                if (pre==suf)   break;
            }
            Bignum tmp=Bignum(strsec+strfir.substr(l));
            tmp.minus(Bignum("1"));
            
            
            if (tmp<final){
                final=tmp;firsta=strsec.size()-l;
            }
        }
        /*3 over*/
        if (all9(final))    firsta--;
        if (final.size==1){
            //cout<<final<<endl;
            cout<<final<<endl;
            return 0;
        }
        
        /*calculate answer*/
        int wid=final.size;
        ans=Bignum(string(wid-1,'1'));
        
        Bignum pow("1");
        for (int i=0;i<wid-1;i++)   pow=pow*Bignum("10");
        
        pow=pow*Bignum(itoa(wid-1));
        pow.minus(ans);
        ans=pow;
        Bignum tmp=final;
        
        tmp.minus(Bignum("1"+string(wid-1,'0')));
        tmp=tmp*Bignum(itoa(wid));
        tmp.add(Bignum(itoa(firsta)));
        tmp.add(Bignum("1"));
        ans.add(tmp);
        //cout<<ans;
        cout<<ans<<endl;
    return 0;
    }
    
    string itoa(int a)
    {
        stringstream ss;
        ss<<a;
        string ret;
        ss>>ret;
        return ret;
    }
    
    
    bool all9(Bignum& a)
    {
        for (int i=0;i<a.size;i++)
            if (a[i]!=9)    return false;
        return true;
    }
    

    总结:
    1.这道题的代码其实我也不知道是否正确,因为网上找不到真正正确的题解和数据,所以仅附思路和代码,如果发现问题,欢迎指正。
    2.这道题与其说是考算法,不如说是考编程能力和细心程度。不管是划分时的复杂情况,还是高精度类,都有一定的难度。

    相关文章

      网友评论

          本文标题:洛谷2454 [SDOI2006]数字串位置

          本文链接:https://www.haomeiwen.com/subject/etgngqtx.html