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",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)\n",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("\n");
    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\n",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\n",dp[n][n][n][n]);
    return 0;
}
View Code

 

posted @ 2020-07-02 23:22  lonely_wind  阅读(435)  评论(0编辑  收藏  举报