寒假训练1(分析与总结)

此次训练有原题,以巩固基础为目的,理应达到AK,但在Dp方面有所欠缺。

完美字符串(51Nod - 1182)

题意

约翰认为字符串的完美度等于它里面所有字母的完美度之和。每个字母的完美度可以由你来分配,不同字母的完美度不同,分别对应一个1-26之间的整数。约翰不在乎字母大小写。(也就是说字母F和f)的完美度相同。给定一个字符串,输出它的最大可能的完美度。例如:dad,你可以将26分配给d,25分配给a,这样整个字符串完美度为77。
Input
输入一个字符串S(S的长度 <= 10000),S中没有除字母外的其他字符。
Output
由你将1-26分配给不同的字母,使得字符串S的完美度最大,输出这个完美度。

思路与小结

这道题没什么特别,将字符串用strlwr强转小写。然后运用贪心,统计每个字母出现的次数,出现次数最多的肯定完美度越高越好。
我WA的一次是因为没有看到题目中的不在乎字母的大小写,所以没有让字符串全部转为小写。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define MAXN 10050

int a[30];
char s[MAXN];

int main()
{
    scanf("%s",s);
    strlwr(s);
    int len=strlen(s);
    for(int i=0;i<len;i++)
        a[s[i]-'a']++;
    sort(a,a+26);
    int sum=0,now=26;
    for(int i=25;i>=0;i--)
        sum+=a[i]*(i+1);
    printf("%d",sum);
}

编辑距离

题意

编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
例如将kitten一字转成sitting:
sitten (k->s)
sittin (e->i)
sitting (->g)
所以kitten和sitting的编辑距离是3。俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。
给出两个字符串a,b,求a和b的编辑距离。
Input
第1行:字符串a(a的长度 <= 1000)。
第2行:字符串b(b的长度 <= 1000)。
Output
输出a和b的编辑距离

思路与小结

这是一个十分经典的Dp问题,我们定义d[i][j]表示在第一个串的i位置,第二个串的j位置的最小编辑距离。目标状态是d[n][m],转移的时候我们只需注意a[i]==b[j]的情况,也就是当前两个串位置上的字符相同的情况,我们就不需要更改,等于d[i-1][j-1]就好了。其他情况分别+1,然后取min。注意初始化的时候将d[0][i]=i,d[i][0]=i这样才对,而不是全是零。
综上

  • d[i][0]=i,d[0][j]=j(1<=i<=n,1<=j<=m)
  • d[i][j]=min(d[i-1][j-1]+(a[i]==b[j]?0:1),min(d[i-1][j],d[i][j-1])+1);
  • ans=d[n][m]

我之前错的那一次就是因为初始化的时候全部为0,并没与考虑这样后果。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define INF 0x3fffffff

int d[1005][1005];
char a[1005],b[1005];

int main()
{
    int n,m;
    scanf("%s%s",a+1,b+1);
    n=strlen(a+1);
    m=strlen(b+1);
    for(int i=1;i<=n;i++)
        d[i][0]=i;
    for(int i=1;i<=m;i++)
        d[0][i]=i;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(a[i]!=b[j])
                d[i][j]=d[i-1][j-1]+1;
            else
                d[i][j]=d[i-1][j-1];
            d[i][j]=min(d[i][j],min(d[i-1][j],d[i][j-1])+1);
        }
    printf("%d",d[n][m]);
}

旋转字符串(51Nod - 1347)

题意

S0…n−1是一个长度为n的字符串,定义旋转函数Left(S)=S1…n−1+S0.比如S=”abcd”,Left(S)=”bcda”.一个串是对串当且仅当这个串长度为偶数,前半段和后半段一样。比如”abcabc”是对串,”aabbcc”则不是。
现在问题是给定一个字符串,判断他是否可以由一个对串旋转任意次得到。
Input
第1行:给出一个字符串(字符串非空串,只包含小写字母,长度不超过1000000)
Output
对于每个测试用例,输出结果占一行,如果能,输出YES,否则输出NO。

思路与小结

一开始,我理解题意 前半段和后半段一样 的意思是字母组成相同就可以这种奇怪的想法可以跑过样例。然后我的思路就很奇怪了。用滑窗维护这一半的区间中元素的个数,是否是一半就可以了。但是令我震惊的是这个奇怪的想法过了19组数据,只剩一组没过。
于是开始反思,发现只需要O(n)扫一遍,判断这个位置i是否与(i+len/2)%n位置上的字符相等即可。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

char s[1000005];

int main()
{
    while(~scanf("%s",s))
    {
        int len=strlen(s);
        if(len&1)
        {
            printf("NO\n");
            continue;
        }
        bool flag=0;
        for(int i=0;i<len;i++)
        {
            if(s[(i+len/2)%len]!=s[i])
            {
                printf("NO\n");
                flag=1;
                break;
            }
        }
        if(flag)
            continue;
        printf("YES\n");
    }
}

大鱼吃小鱼(51Nod - 1289)

题意

有N条鱼每条鱼的位置及大小均不同,他们沿着X轴游动,有的向左,有的向右。游动的速度是一样的,两条鱼相遇大鱼会吃掉小鱼。从左到右给出每条鱼的大小和游动的方向(0表示向左,1表示向右)。问足够长的时间之后,能剩下多少条鱼?
Input
第1行:1个数N,表示鱼的数量(1 <= N <= 100000)。
第2 - N + 1行:每行两个数Aii, Bii,中间用空格分隔,分别表示鱼的大小及游动的方向(1 <= Aii <= 10^9,Bii = 0 或 1,0表示向左,1表示向右)。
Output
输出1个数,表示最终剩下的鱼的数量。

思路和小结

这道题的基本思路可以说是十分简单的,因为鱼只有两个方向,要么向左,要么向右。所以我们将一个方向的鱼放进栈里,然后另一个方向我们在扫过去的过程中进行处理,如果大于当前栈顶的鱼,栈弹出,这条鱼被吃了;如果小于就继续扫描。反正当两条鱼相遇时,必定有一条鱼死…

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

int main()
{
    stack<int> s;
    int n;
    scanf("%d",&n);
    int ans=n;
    while(n--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(y==1)  s.push(x);
        else
        {
            while(!s.empty())
            {
                if(x>s.top())
                {
                    s.pop();
                    ans--;
                }
                else
                {
                    ans--;
                    break;
                }
            }
        }
    }
    printf("%d\n",ans);
}

质数中的质数(质数筛法)

题意

如果一个质数,在质数列表中的编号也是质数,那么就称之为质数中的质数。例如:3 5分别是排第2和第3的质数,所以他们是质数中的质数。现在给出一个数N,求>=N的最小的质数中的质数是多少(可以考虑用质数筛法来做)。
Input
输入一个数N(N <= 10^6)
Output
输出>=N的最小的质数中的质数。

思路与小结

普通的线性筛,在筛出质数的同时,计数。因为之前筛的过程中处理了一个vis数组,可以直接访问到当前的个数是不是个质数,当这个数满足要求时直接输出就好了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

#define MAXN 1000000

int n;
bool vis[MAXN+5];
vector <int> prime;

void Init()
{
    int cnt=0;
    vis[1]=1;
    for(int i=2;i<=MAXN;i++)
    {
        if(!vis[i])
        {
            cnt++;
            prime.push_back(i);
            if(i>=n&&!vis[cnt])
            {
                printf("%d\n",i);
                exit(0);
            }
        }
        for(int j=0;j<(int)prime.size();j++)
        {
            int k=prime[j]*i;
            if(k>MAXN)
                break;
            vis[k]=1;
            if(i%prime[j]==0)
                break;
        }
    }
}

int main()
{
    scanf("%d",&n);
    Init();
}

齐头并进(51Nod - 1649)

题意

在一个叫奥斯汀的城市,有n个小镇(从1到n编号),这些小镇通过m条双向火车铁轨相连。当然某些小镇之间也有公路相连。为了保证每两个小镇之间的人可以方便的相互访问,市长就在那些没有铁轨直接相连的小镇之间建造了公路。在两个直接通过公路或者铁路相连的小镇之间移动,要花费一个小时的时间。
现在有一辆火车和一辆汽车同时从小镇1出发。他们都要前往小镇n,但是他们中途不能同时停在同一个小镇(但是可以同时停在小镇n)。火车只能走铁路,汽车只能走公路。
现在请来为火车和汽车分别设计一条线路;所有的公路或者铁路可以被多次使用。使得火车和汽车尽可能快的到达小镇n。即要求他们中最后到达小镇n的时间要最短。输出这个最短时间。(最后火车和汽车可以同时到达小镇n,也可以先后到达。)
Input
单组测试数据。
第一行有两个整数n 和 m (2≤n≤400, 0≤m≤n*(n-1)/2) ,表示小镇的数目和铁轨的数目。
接下来m行,每行有两个整数u 和 v,表示u和v之间有一条铁路。(1≤u,v≤n, u≠v)。
输入中保证两个小镇之间最多有一条铁路直接相连。
Output
输出一个整数,表示答案,如果没有合法的路线规划,输出-1。

思路与小结

从题目大概可以看出任意两个城市之间有且只有一条路,要么是铁路,要么是公路。所以这就保证了两个路径之间是没有共同的点的。直接两遍DJ就可以了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

int n,m;
int a[500][500],d[500],vis[500];

int d1()
{
    queue <int> q;
    memset(vis,0,sizeof vis);
    memset(d,-1,sizeof d);
    d[1]=0;
    vis[1]=1;
    q.push(1);
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        vis[u]=0;
        for(int v=1;v<=n;v++)
        {
            if(v==u||!a[u][v])
                continue;
            if(d[v]==-1||d[u]+1<d[v])
            {
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
                d[v]=d[u]+1;
            }
        }
    }
    //printf("##%d\n",d[n]);
    return d[n];
}

int d2()
{
    queue <int> q;
    memset(vis,0,sizeof vis);
    memset(d,-1,sizeof d);
    d[1]=0;
    vis[1]=1;
    q.push(1);
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        vis[u]=0;
        for(int v=1;v<=n;v++)
        {
            if(v==u||a[u][v])
                continue;
            if(d[v]==-1||d[u]+1<d[v])
            {
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
                d[v]=d[u]+1;
            }
        }
    }
    return d[n];
}

int main()
{
    scanf("%d%d",&n,&m);
    if(!m)
    {
        printf("-1");
        return 0;
    }
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        a[u][v]=a[v][u]=1;
    }
    int ans=d1();
    if(ans==-1)
    {
        printf("-1");
        return 0;
    }
    int ans2=d2();
    if(ans2==-1)
    {
        printf("-1");
        return 0;
    }
    printf("%d",max(ans,ans2));
}

教育改革(51Nod - 1636)

题意

最近A学校正在实施教育改革。

一个学年由n天组成。A学校有m门课程,每天学生必须学习一门课,一门课程必须在一天内学习完。在学习完第i门课程后,学生们会收到 xixi 个家庭作业,其中 xi是区间[ai,bi]里的一个整数xi是区间[ai,bi]里的一个整数 。每门课还有一个属性,就是复杂度 cici 。A学校现在要制他们的课程表,具体要求如下:
・在课程表中,随着天数的增加,课程的复杂度是严格递增的。
・除了第1天,每天的作业量必须是前一天的k倍,或者比前一天多k个作业。(假设第i天的作业量为 xixi ,则对于i(1<i≤n)到满足 xi = k+xi−1xi = k+xi−1 或 xi = k・xi−1xi = k・xi−1 );
现在,给定天数n,系数k,和m门课程的ai,bi,ci(1≤i≤m)。要求计算一个学年可以安排最大的总作业量( 总作业量的表达式是∑ni=1xi总作业量的表达式是∑i=1nxi )是多少。
Input
单组测试数据
第一行,三个由空格隔开的整数n,m,k(1≤n≤m≤50,1≤k≤100),表示一个学年的天数,课程的数量,和作业增量系数。
接下来的m行,
每行有三个整数,ai,bi,ci(1≤ai≤bi≤10^16,bi-ai≤100,1≤ci≤100)
分别表示第i门课程的最小作业量,和最多作业量,以及复杂度。
不同的课程可以有相同的复杂度。课程编号从1到m。
Output
如果有可行方案,第一行输出“YES”(没有引号),第二行输出最大的作业量。
如果没有可行方案,则输出一行“NO”(没有引号)。

思路与小结

这道是道Dp题可以一眼看出,但是对于Dp比较弱的我来说Dp的方程式和转移有些问题了。我开始想的是二维的Dp,但似乎并不能推出转移式来,所以一直僵持着。直到后来听同学讲了之后才明白。这种题似乎要多练练了。
因为bi-ai<=100,所以xi-ai并不会很大,所以我们通过这个进行状态定义。
d[i][j][k]表示前i门课程里选择了j门,并且第j门为课程i,且课程i布置的作业数量为ai+k,所能布置的最大总作业量。
这样在转移的时候比较好想,分为两种考虑取max就好了。最终状态在d[i][n][j]之间找最大值。
虽然想出来,但似乎写的时候有些问题,还没过,过了之后将代码贴出。

花钱买车牌 (51Nod - 1621)

题意

一个车牌号由n位数字组成。如果一个车牌至少有k位数字是相同的,那么我们就说这个车牌漂亮的车牌。现在华沙想要改变他自己的车牌,使得他的车牌变得漂亮。当然,改车牌是要花钱的。每改变一位数字所要花费的费用等于当前位上的新旧数字之差的绝对值。那么总费用就是每位上所花费用的总和。
举例如下,
旧牌为0123,新牌为7765,那么对应第一位所花费用为|0-7|=7,第二位为|1-7|=6,第三位为|2-6|=4,第四位为|3-5|=2,总和为7+6+4+2=19
华沙想用最少的钱,使他的车牌变得漂亮起来。现在给定n,k,和旧牌的号码,计算换牌的最少费,以及新牌的号码,
如果最少费用的号码有多个,我们取字典序最小的那个。
两个长度为n的序列比较方法如下。
存在两个序列x,y,长度都是n。
如果存在i(1≤i≤n)和任意j(1≤j<i)使得 xi<yixi<yi 并且 xj=yjxj=yj ,那么我们就说x比y小。
Input
单组测试数据
第一行,两个由空格隔开的数字n和k(2≤n≤10^4,2≤k≤n),表示旧牌的位数,和至少要有k位数字相同才能构成漂亮的车牌。
第二行有n位数字,代表华沙的旧车牌。(旧车牌中只有数字)。
Output
共两行,
第一行,一个整数,代表换牌的最小费用,
第二行,n位数字,表示新的车牌。
如果最小费用的车牌有多个,输出字典序最小的那个

思路与小结

这道题我认为难点在于字典序输出这个问题,最小费用可以用贪心的办法求得。然而字典序呢需要考虑的细节特别多。其他的东西也就没什么了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

#define MAXN 21000

int n,sk,mt[15][MAXN+5],num[15],to[MAXN+5],a[MAXN+5];
char S[MAXN+5],last[MAXN+5],ch[MAXN+5];

int work(int v)
{
    int sum=0,cnt=0,s=1,l=v-1,r=v+1;
    while(s<=num[v]&&cnt<sk)
        to[mt[v][s]]=1,s++,cnt++;
    strcpy(ch,S);
    if(cnt==sk) return sum;
    while(cnt<sk)
    {
        s=1;
        while(r<10&&s<=num[r]&&cnt<sk)
            to[mt[r][s]]=1,s++,cnt++,sum+=r-v;
        if(l>=0)
        s=num[l];
        while(l>=0&&s>=1&&cnt<sk)
            to[mt[l][s]]=1,s--,cnt++,sum+=v-l;
        r++,l--;
    }
    for(int i=0;i<n;i++)
        if(to[i])
            ch[i]='0'+v;
    return sum;
}

int main()
{
    scanf("%d%d",&n,&sk);
    for(int i=0;i<=10;i++) num[i] = 0;
        scanf("%s",S);
    for(int i=0;i<n;i++)
        mt[S[i]-'0'][++num[S[i]-'0']]=i;
    int ans=0x3fffffff;
    for(int v=0;v<10;v++)
    {
        for(int j=0;j<n;j++)
            to[j]=0;
        int sum=work(v);
        if(sum<ans||(ans==sum&&(strcmp(ch,last)<0)))
            ans=sum,strcpy(last,ch);
    }
    printf("%d\n%s\n",ans,last);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值