非精写版-51nod基础训练(持续更新)

题目就不赘述了,来源51nod网站。

最长公共子序列LCS

这道题是一道非常好想的二维dp问题,可以在不选当前行,不选当前列的情况下(dp[i+1][j+1]=dp[i][j]+1),若两个字符串中的字符相等,则有在原来的子序列基础上又增加一个字符;若两个字符串中的字符不相等,则寻找到此为止的最大的子序列的长度是多少,把值赋给它。
dp[i][j]:表示第一个字符串取(0,i-1),第二个字符串为(0,j-1),最长的公共子序列是多长。
最后在记录长度的同时,记录这个子序列的长度是怎么加过来的,记录完毕后从终点回溯到起点,用字符串保存回溯中找到的子序列的字符,翻转(因为是从终点到起点),输出。

#include
using namespace std;
const int maxn=1010;
int dp[maxn][maxn];
char opp[maxn][maxn];
int main(){
      
    string s,str;
    cin>>s>>str;
    dp[0][0]=0;
    for(int i=0;i<s.size();i++){
         
        for(int j=0;j<str.size();j++){
     
            if(s[i]==str[j]){
     
                dp[i+1][j+1]=dp[i][j]+1;
                opp[i][j]='x';
            }else{
     
                if(dp[i][j+1]>dp[i+1][j]){
     
                    dp[i+1][j+1]=dp[i][j+1];
                    opp[i][j]='d';
                }else{
     
                    dp[i+1][j+1]=dp[i+1][j];
                    opp[i][j]='r';
                }
            }
        }
    }
    string ans;
    int x=s.size()-1,y=str.size()-1;
    while(x>=0&&y>=0){
     
        if(opp[x][y]=='x'){
     
            ans+=s[x];
            x--;
            y--;
        }else if(opp[x][y]=='d'){
     
            x--;
        }else{
     
            y--;
        }
    }
    reverse(ans.begin(),ans.end());
    cout<<ans<<endl;
    //system("pause");
	return 0;
}

大数加法(考虑负数)

这就有点灵性了,乱写了一个,模拟的加减法竖式。
调了半天,没考虑前置零,考虑了之后又忘了还有结果为零的情况。
然后就分类讨论就可,我确实是写麻烦了,就当练练大模拟了。

#include
using namespace std;
string add(string a,string b){
     
    int fa=1,fb=1;
    int lena=a.size(),lenb=b.size();
    if(a[0]<'0'||a[0]>'9') fa=-1,a=a.substr(1,lena-1),lena--;
    if(b[0]<'0'||b[0]>'9') fb=-1,b=b.substr(1,lenb-1),lenb--;
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    string ans;
    int shi=0;
    int tem=0;
    int aa=0,bb=0;
    ans="";
    if((fa>0 && fb<0) || (fa<0 && fb>0)){
     
        //a-b
        if(lena>lenb || (lena==lenb && a[lena-1]>b[lenb-1])){
     
            for(int i=0;i<lenb;i++){
     
                aa=a[i]-'0';
                bb=b[i]-'0';
                tem=aa-bb+shi;
                if(tem<0){
     
                    tem=tem+10;
                    shi=-1;
                }else{
     
                    shi=0;
                }
                ans+=(tem+'0');
            }
            for(int i=lenb;i<lena;i++){
     
                aa=a[i]-'0';
                tem=aa+shi;
                if(tem<0){
     
                    tem=tem+10;
                    shi=-1;
                }else{
     
                    shi=0;
                }
                ans+=(tem+'0');
            }
        }else{
     
            for(int i=0;i<lena;i++){
     
                aa=a[i]-'0';
                bb=b[i]-'0';
                tem=bb-aa+shi;
                if(tem<0){
     
                    tem=tem+10;
                    shi=-1;
                }else{
     
                    shi=0;
                }
                ans+=(tem+'0');
            }
            for(int i=lena;i<lenb;i++){
     
                bb=b[i]-'0';
                tem=bb+shi;
                if(tem<0){
     
                    tem=tem+10;
                    shi=-1;
                }else{
     
                    shi=0;
                }
                ans+=(tem+'0');
            }
            ans+='-';
        }
        //b-a
        
        if(fa<0 && fb>0){
     
            int len=ans.size();
            if(ans[len-1]=='-') ans=ans.substr(0,len-1);
            else ans+='-';
        }
        
    }else{
     
        //(a+b) or -(a+b)
        if(lena>lenb){
     
            for(int i=0;i<lenb;i++){
     
                aa=a[i]-'0';
                bb=b[i]-'0';
                tem=aa+bb+shi;
                shi=tem/10;
                tem=tem%10;
                ans+=(tem+'0');
            }
            for(int i=lenb;i<lena;i++){
     
                aa=a[i]-'0';
                tem=aa+shi;
                shi=tem/10;
                tem=tem%10;
                ans+=(tem+'0');
            }
        }else{
     
            for(int i=0;i<lena;i++){
     
                aa=a[i]-'0';
                bb=b[i]-'0';
                tem=aa+bb+shi;
                shi=tem/10;
                tem=tem%10;
                ans+=(tem+'0');
            }
            for(int i=lena;i<lenb;i++){
     
                bb=b[i]-'0';
                tem=bb+shi;
                shi=tem/10;
                tem=tem%10;
                ans+=(tem+'0');
            }
        }
        if(shi) ans+=(shi+'0');
        if(fa<0) ans+='-';
    }
    int cnt=0;
    int f=0;
    int len=ans.size();
    for(int i=len-1;i>=0;i--){
     
        if(ans[i]=='-'){
     
            f=1;
            continue;
        }
        if(ans[i]=='0') cnt++;
        else break;
    }
    if(f) cnt++;
    ans=ans.substr(0,len-cnt);
    if(ans==""){
     
        return "0";
    }
    if(f) ans+='-';
    reverse(ans.begin(),ans.end());
    return ans;
}
int main(){
     
    string a,b;
    cin>>a>>b;
    string ans;
    ans=add(a,b);
    cout<<ans<<endl;
    //system("pause");
    return 0;
}

day2

逆序对

  • 方法一:归并排序求逆序对。
    归并排序采用分而治之的思想,使复杂度降低到 O ( n l o g n ) O(nlog n) O(nlogn),在每一次回溯的过程中如果发现后面的数比前面的数大,就记录一下要向前移动多少位,思考一下为什么 p = l p=l p=l q = m i d + 1 q=mid+1 q=mid+1
#include
using namespace std;
const int maxn=50010;
typedef long long ll;
ll a[maxn],b[maxn];
ll cnt;
void merge_sort(int l,int r){
     
    if(r-l>0){
     
        int mid=(l+r)/2;
        int i=l;
        int p=l,q=mid+1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        while(p<=mid || q<=r){
     
            if(q>r||(p<=mid&&a[p]<=a[q])){
     
                b[i++]=a[p++];
            }else{
     
                b[i++]=a[q++];
                cnt=cnt+mid-p+1;
            }
        }
        for(i=l;i<=r;i++){
     
            a[i]=b[i];
        }
    }
}
int main(){
     
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
     
        cin>>a[i];
    }
    merge_sort(1,n);
    cout<<cnt<<endl;
    //system("pause");
    return 0;
}
  • 方法二:树状数组求逆序对
    看了一篇好博客
    里面离散化解释的特别好,如果给定的数值很分散,跨度很大,我们就用一个新数组储存这些数值的下标,根据它们值的大小对下标排序。
    思考:
    接下来就是由大到小排序,(我们现在有的是对 a [ ] a[] a[]数组排好序的对应的下标数组 b [ ] b[] b[])按照这个逻辑,实际我们是在寻找数列中的正序对,x进入数组后要去找有多少个数是比它小的(因为如果有,一定是在x之前进入树状数组,一定是排在x之前的数,自然也一定是个正序对)。
    那怎么找x之前有多少个比它小的数,在每个数x放入数组之时,更新[x,n]中的所有二的整数倍的数组下标中的值,记录在t[]数组中。
    那么 t [ x ] t[x] t[x] 就是在[1,x]中已经存在了多少个小于x的数(好像也不应该这么说,[x+1,n]也更新了,但是我们只用到[1,x]的区间)。
#include
using namespace std;
const int maxn=50010;
typedef long long ll;
ll a[maxn],b[maxn];
ll ans;
ll t[maxn];
int n;
int lowbit(int x){
     
    return x&-x;
}
void add(int x){
     
    while(x<=n){
     
        t[x]++;
        x+=lowbit(x);
    }
}
ll sum(int x){
     
    ll res=0;
    while(x>=1){
     
        res+=t[x];
        x-=lowbit(x);
    }
    return res;
}
bool cmp(int x,int y){
     
    if(a[x]==a[y]) return x>y;
    return a[x]>a[y];
}
int main(){
     
    cin>>n;
    for(int i=1;i<=n;i++){
     
        cin>>a[i];
        b[i]=i;
    }
    sort(b+1,b+1+n,cmp);
    for(int i=1;i<=n;i++){
     
        add(b[i]);
        ans+=sum(b[i]-1);
    }
    cout<<ans<<endl;
    return 0;
}

大数乘法

依旧大模拟,写的时候出了点差错:

terminate called after throwing an instance of ‘std::out_of_range’ what(): basic_string::substr
解决方案:查找substr方法前后代码,排除可能的越界条件。

其它的异常顺利。

#include
using namespace std;
const int maxn=50010;
typedef long long ll;
string add(string a,string b){
     
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    string ans;
    int shi=0;
    int tem=0;
    int aa=0,bb=0;
    int lena=a.size(),lenb=b.size();
    ans="";
    for(int i=0;i<lena;i++){
     
        aa=a[i]-'0';
        bb=b[i]-'0';
        tem=aa+bb+shi;
        shi=tem/10;
        tem=tem%10;
        ans+=(tem+'0');
    }
    for(int i=lena;i<lenb;i++){
     
        bb=b[i]-'0';
        tem=bb+shi;
        shi=tem/10;
        tem=tem%10;
        ans+=(tem+'0');
    }
    if(shi) ans+=(shi+'0');
    int cnt=0;
    int len=ans.size();
    for(int i=len-1;i>=0;i--){
     
        if(ans[i]=='0') cnt++;
        else break;
    }
    ans=ans.substr(0,len-cnt);
    if(ans==""){
     
        return "0";
    }
    reverse(ans.begin(),ans.end());
    return ans;
}
string mul(string a,string b){
     
    int fa=1,fb=1;
    int lena=a.size(),lenb=b.size();
    if(a[0]=='-'){
     fa=-1;a.substr(1,lena-1);lena--;}
    if(b[0]=='-'){
     fb=-1;b.substr(1,lenb-1);lenb--;}
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    if(lenb>lena){
     
        string temp=a;
        a=b;
        b=temp;
        int tt=lena;
        lena=lenb;
        lenb=tt;
    }
    vector<string> ve;
    int num=0;
    for(int i=0;i<lenb;i++){
     
        string ans;
        int p=b[i]-'0';
        int shi=0;
        for(int j=0;j<lena;j++){
     
            num=(a[j]-'0')*p+shi;
            shi=num/10;
            num%=10;
            ans+=(num+'0');
        }
        string sh;
        sh="";
        sh=(shi+'0');
        ans+=sh;
        reverse(ans.begin(),ans.end());
        for(int j=0;j<i;j++){
     
            ans+="0";
        }
        ve.push_back(ans);
        shi=0;
        num=0;
    }
    string s,str;
    s=ve[0];
    for(int i=1;i<ve.size();i++){
     
        s=add(s,ve[i]);
    }
    int cnt=0;
    int len=s.size();
    for(int i=0;i<len;i++){
     
        if(s[i]=='0') cnt++;
        else break;
    }
    s=s.substr(cnt,len-cnt);
    if(s==""){
     
        return "0";
    }
    if(fa<0 && fb<0){
     
        ;
    }else if(fa>0 && fb>0){
     
        ;
    }else{
     
        str="-";
    }
    str+=s;
    return str;
}
int main(){
     
    string a,b;
    cin>>a>>b;
    cout<<mul(a,b)<<endl;
    //system("pause");
    return 0;
}

day3

N的阶乘

思路是:用一个 A [ i ] A[i] A[i]数组储存,每个数组中储存14位数,能把运算控制在long long 的范围之中,第一重循环写的是该乘哪个数了(1~n中),第二重是用了几个 A [ i ] A[i] A[i],用了 l l l个。 c c c保存的是存不下的进位数,如果有,放到下一个 A [ i ] A[i] A[i]中,并用 l l l更新,最后通过循环输出结果,把数组拼接在一起展示。

printf("%0.14lld",...);
//表示取十四位长整型数输出
#include
using namespace std;
typedef long long ll;
#define mod 100000000000000
ll A[10000000];
int main(){
     
    ll n;
    scanf("%lld",&n);
    A[0]=1;
    ll i,j,l=0;
    for(i=1;i<=n;i++){
     
        ll c=0;
        for(j=0;j<=l;j++){
     
            ll t=A[j]*i+c;
            A[j]=t%mod;
            c=t/mod;
        }
        if(c!=0){
     
            A[++l]=c;
        }
    }
    printf("%lld",A[l]);
    for(int i=l-1;i>=0;i--){
     
        printf("%0.14lld\n",A[i]);
    }
    //system("pause");
    return 0;
}

N的阶乘的长度

根据对数的性质: l o g 10 ( x ) log10(x) log10(x) x x x如果是10,结果为1,一位;为100,结果是2,三位…
我们可以总结出, n ! n! n!有多少位,就是算 l o g 10 ( n ! ) log10(n!) log10(n!)的结果再加一。
数据范围是 1 0 6 10^6 106,我们可以用线性的累加法做出结果。
最后取整的时候用了floor函数,但是在小数点位数很多的时候发生了错误;采用强制取整,得到了最终正确的答案。

#include
using namespace std;
int main(){
     
    double ans=0;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
     
        ans+=log10(i*1.0);
    }
    cout<<ans<<endl;
    cout<<(long long)(ans)+1<<endl;
    //system("pause");
    return 0;
}

博弈论(三个简单的博弈模式)

Bash游戏

A,B两个人拿一堆石子,共n个,每次取[1,k]个,谁先拿完谁赢。轮流拿,A先手。

如果n是k+1的倍数,那么无论A取任意值x,B只要取k+1-x即可保证必胜。
同理,若n不是k+1的倍数,那么只要第一步取n%(k+1),然后就相当于AB换了身份,显然A必胜。

#include
using namespace std;
typedef long long ll;
ll n,k;
int t;
int main(){
     
    cin>>t;
    while(t--){
     
        cin>>n>>k;
        if(n%(k+1)) cout<<"A"<<endl;
        else cout<<"B"<<endl;
    }
    return 0;
}

Nim游戏

A,B两人拿n堆石子,每堆石子 [ 1 , ∞ ] [1,\infty] [1,]个,每次可以最少拿一个,最多拿一堆,最先拿完的获胜。A先手。

#include
using namespace std;
typedef long long ll;
int n;
ll a;
ll ans;
int main(){
     
    cin>>n;
    for(int i=1;i<=n;i++){
     
        cin>>a;
        ans^=a;
    }
    if(ans) cout<<"A"<<endl;
    else cout<<"B"<<endl;
    //system("pause");
    return 0;
}

威佐夫游戏

你可能感兴趣的:(铜牌刷题路)