zoukankan      html  css  js  c++  java
  • 2000NOIP提高组(全解)

    题目链接与说明:

    进制转换(负进制的处理)

    https://www.luogu.com.cn/problem/P1017

    乘积最大(DP+大整数)

    https://www.luogu.com.cn/problem/P1018

    单词接龙(DFS+暴力)           

    https://www.luogu.com.cn/problem/P1019  

     方格取数(DP)

    https://www.luogu.com.cn/problem/P1004

     

    进制转换

    如果是一般的进制转换的话很容易做,我们一直取余然后将其倒置输出就OK了,比如:

    14(10)=14/2=7...0

    7/2=3...1

    3/2=1...1

    1/2=0...1

    那么也就是说10进制下的14在2进制下表示为1110

    但这里变成了负进制的话就需要改改了。我们必须知道的是做除法的时候余数不能为负数,那么事情就好办了,我们一样地做除法,一旦碰到余数为负数的情况,我们将商+1,同时将余数减去被除数就好了,比如:

    14(10)=14/-2=-7...0

    -7/-2=3...-1

    3+1=4,-1-(-2)=1

    4/-2=-2...0

    -2/-2=1...0

    1/-2=0...1

    所以14在-2进制下的表示为10010

    那么程序也就很简单了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    vector<int>g;
    char use[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L'};
    
    int main()
    {
        int n,r;
        scanf ("%d%d",&n,&r);
        int cpn=n;
        while(n){
            int p=n%r;
            n/=r;
            printf("%d %d
    ",n,p );
            if (p<0) p-=r,n+=1;
            g.push_back(abs(p));
        }
        reverse(g.begin(),g.end());
        printf ("%d=",cpn);
        for (auto x:g)
            printf ("%c",use[x]);
        printf ("(base%d)
    ",r);
        return 0;
    }
    View Code

    乘积最大

    emmm,一眼望过去就知道是个DP,设置$dp[i][j]$表示为第$i$位数后面放第$j$个乘号的最大值。第一个乘号不用转移,直接将前面的字符串截取就好了,从第二个乘号开始转移,我们往前寻找,如果当前要放的是第$j$个乘号,那么我们就看看前面每一位是否存在第$j-1$个乘号,如果存在就进行转移,同时转移的时候打上存在标记。那么转移的方程就挺好写了,转移部分代码如下:

    for (int i=1; i<n; i++) { //在第i位后放第j个乘号
        dp[i][1]=deal(1,i);
        dp[i][1].vis=1;
        for (int j=2; j<=m; j++)
            for (int k=j-1; k<i; k++) { //第k个位置后面是否存在第j-1个乘号,并转移
                if (!dp[k][j-1].vis) continue;
                dp[i][j]=Max(dp[i][j],mul(dp[k][j-1],deal(k+1,i)));
                dp[i][j].vis=1;
            }
        if (!dp[i][m].vis) continue;
        ans[i]=mul(dp[i][m],deal(i+1,n));
    }

    接下来就是大整数部分,这个就没什么好说的了。

    值得注意的是,ans[i]表示的最后一个乘号放在第$i$位的最大值,所以我们还得再循环一遍将其中的最大值找出来

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=50;
    
    char s[50];
    int a[mac];
    
    struct node
    {
        int v,c[600],vis;
        node (){v=0; vis=0; memset(c,0,sizeof c);}
    
        void rev(){reverse(c+1,c+1+v);}
    }dp[mac][10],ans[mac];
    
    node deal(int st,int ed)
    {
        node b;
        int p=0;
        for (int i=st; i<=ed; i++)
            b.c[++p]=a[i];
        b.v=ed-st+1;
        return b;
    }
    
    node Max(node x,node y)
    {
        if (x.v==y.v){
            for (int i=1; i<=x.v; i++){
                if (x.c[i]>y.c[i]) return x;
                if (x.c[i]<y.c[i]) return y;
            }
        }
        if (x.v>y.v) return x;
        return y;
    }
    
    node mul(node x,node y)
    {
        node z;
        int cnt=0,base=0,tail1=x.v,tail2=y.v;
        for (int i=tail1; i>=1; i--){
            cnt++;base=0;
            for (int j=tail2; j>=1; j--){
                z.c[cnt+base]+=x.c[i]*y.c[j];
                z.v=max(z.v,cnt+base);
                base++;
            }
        }
        for (int i=1; i<=z.v; i++){
            if (i>100) break;
            if (z.c[i]>=10) {
                z.c[i+1]+=z.c[i]/10;
                z.c[i]%=10;
                if (i==z.v) z.v++;
            }
        }
        z.rev();
        return z;
    }
    
    int main()
    {
        int n,m;
        scanf ("%d%d",&n,&m);
        scanf ("%s",s+1);
        for (int i=1; i<=n; i++)
            a[i]=s[i]-'0';
        for (int i=1; i<n; i++){//在第i位后放第j个乘号
            dp[i][1]=deal(1,i);
            dp[i][1].vis=1;
            for (int j=2; j<=m; j++)
                for (int k=j-1; k<i; k++){//第k个位置后面是否存在第j-1个乘号,并转移
                    if (!dp[k][j-1].vis) continue;
                    dp[i][j]=Max(dp[i][j],mul(dp[k][j-1],deal(k+1,i)));
                    dp[i][j].vis=1;
                }
            if (!dp[i][m].vis) continue;
            ans[i]=mul(dp[i][m],deal(i+1,n));
        }
        node last_ans;
        for (int i=1; i<n; i++)
            last_ans=Max(last_ans,ans[i]);
        for (int i=1; i<=last_ans.v; i++) printf("%d",last_ans.c[i]);
        printf("
    ");
        return 0;
    }
    View Code

    单词接龙

    emmm,感觉没什么好说的,就是个暴力DFS的过程,不过用string来做的话感觉容易一些。对每个含龙头字母的字符串开始DFS,然后对每一个字符串判断是否能拼接,并且得到重复的长度,如下:

    int ok(string p1,string p2,int &same)
    {
        int len1=p1.length(),len2=p2.length();
        int head1=1,head2=0,head=0;//重复的不要,所以head1从1开始
        for (; head1<len1; head1++){
            if (p1[head1]==p2[head2]){
                int cphead=head1;
                while(p1[head1]==p2[head2]){
                    head1++;head2++;
                    if (head2==len2) break;
                    if (head1==len1) break;
                }
                if (head1<len1 || head2==len2) head1=cphead,head2=0;
                else head=max(head,cphead),head1=cphead,head2=0;
            }
        }
        if (head) {
            same=len1-head;
            return 1;
        }
        return 0;
    }

    接下来就是个简单的DFS过程了,进行OK判断完成之后将字符串进行拼接,我们可以使用substr函数来进行,同时在回溯的时候我们可以用erase函数来抹除上一次的拼接

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    string s1,s,word[30];
    int tot=0,ans=0,vis[30];
    
    int ok(string p1,string p2,int &same)
    {
        int len1=p1.length(),len2=p2.length();
        int head1=1,head2=0,head=0;//重复的不要,所以head1从1开始
        for (; head1<len1; head1++){
            if (p1[head1]==p2[head2]){
                int cphead=head1;
                while(p1[head1]==p2[head2]){
                    head1++;head2++;
                    if (head2==len2) break;
                    if (head1==len1) break;
                }
                if (head1<len1 || head2==len2) head1=cphead,head2=0;
                else head=max(head,cphead),head1=cphead,head2=0;
            }
        }
        if (head) {
            same=len1-head;
            return 1;
        }
        return 0;
    }
    
    void dfs(string now,int len,int same)
    {
        for (int i=1; i<=tot; i++){
            if (vis[i]==2) continue;
            if (!ok(now,word[i],same)) continue;
            len+=word[i].length()-same;
            ans=max(ans,len);
            vis[i]++;
            now+=word[i].substr(same);
            dfs(now,len,same);
            len-=word[i].length()-same;
            now.erase(now.begin()+len,now.end());
            vis[i]--;
        }
    }
    
    int main()
    {
        int n,p=0;
        cin>>n;
        for (int i=1; i<=n; i++){
            cin>>s;
            if (s.length()>=2) word[++p]=s;
        }
        cin>>s;
        tot=p;
        for (int i=1; i<=tot; i++){
            if (word[i][0]==s[0]) {
                int uu=word[i].length();
                ans=max(ans,uu);
                vis[i]=1;
                dfs(word[i],uu,0);
                vis[i]=0;
            }
        }
        printf ("%d
    ",ans);
        return 0;
    }
    View Code

    方格取数

    这题也没什么好说的,就是个裸的方格DP,最为简单的方法就是直接定义四维DP,$dp[i][j][k][l]$表示了第一个走到坐标$(i,j)$,第二个走到坐标$(k,l)$的时候的最大值,那么状态的转移也非常易懂,每个人的决策有两种,那么总共也就4种决策,我们的$dp[i][j][k][l]$也就是从上一个最优决策加上本次的收获,但需要值得注意的是,当坐标相同的时候要减去本次的收获。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    int dp[12][12][12][12];
    int mp[12][12];
    
    int main(int argc, char const *argv[])
    {
        int n;
        scanf ("%d",&n);
        int x,y,val;
        while (scanf ("%d%d%d",&x,&y,&val)){
            if (!x && !y && !val) break;
            mp[x][y]=val;
        }
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                for (int k=1; k<=n; k++)
                    for (int l=1; l<=n; l++){
                        dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))+mp[i][j]+mp[k][l];
                        if (i==k && j==l) dp[i][j][k][l]-=mp[i][j];
                    }
        printf ("%d
    ",dp[n][n][n][n]);
        return 0;
    }
    View Code
    路漫漫兮
  • 相关阅读:
    C++学习笔记(十六):友元
    C++学习笔记(十五):异常
    C++学习笔记(十四):模板
    C++学习笔记(十三):类、包和接口
    C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类
    C++学习笔记(十一):void*指针、类型转换和动态内存分配
    C++学习笔记(十):类
    quartz 实现调度任务 SchedulerManager
    Session 活化与钝化 与tomcat钝化驱动器
    web listener
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/13227825.html
Copyright © 2011-2022 走看看