NOIP2008 笨小猴 火柴棒等式 传纸条 双栈排序

NOIP2008

  1. 笨小猴
    (word.pas/c/cpp)
    【问题描述】
    笨小猴的词汇量很小,所以每次做英语选择题的时候都很头痛。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!
    这种方法的具体描述如下:假设maxn是单词中出现次数最多的字母的出现次数,minn是单词中出现次数最少的字母的出现次数,如果maxn-minn是一个质数,那么笨小猴就认为这是个Lucky Word,这样的单词很可能就是正确的答案。
    【输入】
    输入文件word.in只有一行,是一个单词,其中只可能出现小写字母,并且长度小于100。
    【输出】
    输出文件word.out共两行,第一行是一个字符串,假设输入的的单词是Lucky Word,那么输出“Lucky Word”,否则输出“No Answer”;
    第二行是一个整数,如果输入单词是Lucky Word,输出maxn-minn的值,否则输出0。
    【输入输出样例1】
    word.in word.out
    error Lucky Word
    2
    【输入输出样例1解释】
    单词error中出现最多的字母r出现了3次,出现次数最少的字母出现了1次,3-1=2,2是质数。
    【输入输出样例2】
    word.in word.out
    olympic No Answer
    0
    【输入输出样例2解释】
    单词olympic中出现最多的字母i出现了2次,出现次数最少的字母出现了1次,2-1=1,1不是质数。
    第一题
    【无奈】一看到字母就想用map做了,一边读入一边统计次数,过程中更新min和max,最后判断一下就行了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>

using namespace std;

int main(){
    freopen("word.in", "r", stdin);
    freopen("word.out", "w", stdout);
    map<char,int>mp;
    mp.clear();
    char cc[100];
    int maxx = 0, minn = 100;
    int idc = 0;
    while(scanf("%c", &cc[++idc]) != '\n'){
        if(cc[idc] == '\n') break;
        mp[cc[idc]]++;
    }
    for(int i=1; i<idc; i++){
        int sum = mp[cc[i]];
        char cmax, cmin;
        if(sum > maxx){
            maxx = sum;
            cmax = cc[i];
        }
        if(sum < minn){
            minn = sum;
            cmin = cc[i];
        }
    }
    //cout << maxx << ' ' << minn << endl;
    int ans = maxx - minn;
    bool flag = false;
    if(ans == 0 || ans == 1){
        printf("No Answer\n%d", 0);
        return 0;
    }
    for(int i=2; i<=(ans>>1); i++){
        if(ans % i == 0) flag = true;
    }
    if(flag == true){
        printf("No Answer\n%d", 0);
        return 0;
    }
    printf("Lucky Word\n%d", ans);
    return 0;
}
  1. 火柴棒等式
    (matches.pas/c/cpp)
    【问题描述】
    给你n根火柴棍,你可以拼出多少个形如“A+B=C”的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零,则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示:
    这里写图片描述
    注意:
  2. 加号与等号各自需要两根火柴棍
  3. 如果A≠B,则A+B=C与B+A=C视为不同的等式(A、B、C>=0)
  4. n根火柴棍必须全部用上

【输入】
输入文件matches.in共一行,又一个整数n(n<=24)。
【输出】
输出文件matches.out共一行,表示能拼成的不同等式的数目。

【输入输出样例1】
matches.in matches.out
14 2
【输入输出样例1解释】
2个等式为0+1=1和1+0=1。

【输入输出样例2】
matches.in matches.out
18 9
【输入输出样例2解释】
9个等式为:
0+4=4
0+11=11
1+10=11
2+2=4
2+7=9
4+0=4
7+2=9
10+1=11
11+0=11
第二题
由于一开始想不到一个很巧妙的解决方案,所以自然而然就想到了暴力枚举的方法,先做一个判断,因为n<=24,经过简单的推算,加数是小于七百多的(保守一点的话就小于1111,用1111+1=1112进行判断)。于是枚举这一条无上光明的大道就为你敞开了,画风一变,直接枚举,1000*1000,跑完了事。下面的代码中,main里面隐掉的部分是真实的运行,为了追求速度,果断打表【微笑】。

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

int r[10]={6,2,5,5,4,5,6,3,7,6};//为了方便计算用数组形成一个映射
int ans, n, lim;

int get(int s){//计算所用火柴根数
    int p = 0;
    if(s == 0) p = 6;
    while(s){
        p += r[s % 10];
        s /= 10;
    }
    return p;
}

void dfs(int n, int s, int a){//s=0->第一个加数,s=1->第二个加数,a->当前和的值
    if(s == 2){
        if(get(a) == n) ans++;
        return;
    }
    for(int i=0; i<=lim; i++){  
        //if(s==1&&i==a)continue;
        int d = get(i);
        if(d > n) continue;
        dfs(n-d, s+1, a+i);//枚举加数 
    }
    return;
}

int main()
{
    //freopen("out.txt","w",stdout);
    freopen("matches.in","r",stdin);
    freopen("matches.out","w",stdout);
    scanf("%d",&n);
    /*
    lim = 1000;
    ans = 0;
    dfs(n-4,0,0);
    printf("%d\n",ans);
    */
    if(n < 13) printf("0\n");
    if(n == 13) printf("1\n");
    if(n == 14) printf("2\n");
    if(n == 15) printf("8\n");
    if(n == 16) printf("9\n");
    if(n == 17) printf("6\n");
    if(n == 18) printf("9\n");
    if(n == 19) printf("29\n");
    if(n == 20) printf("39\n");
    if(n == 21) printf("38\n");
    if(n == 22) printf("65\n");
    if(n == 23) printf("88\n"); 
    if(n == 24) printf("128\n");    
    return 0;
}
  1. 传纸条
    (message.pas/c/cpp)
    【问题描述】
    小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行 、n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
    在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
    还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

【输入】
输入文件message.in的第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。

【输出】
输出文件message.out共一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

【输入输出样例】
message.in message.out
3 3
0 3 9
2 8 5
5 7 0 34

【限制】
30%的数据满足:1<=m,n<=10
100%的数据满足:1<=m,n<=50
第三题
这是一个特别明显的dp题目,问题是两条路的最优,我们没有办法用常规的方式求两次,因为如果我们分先后求这两条路的话,并没有办法解决路途不能交叉的限定。那么怎么办呢??这里有一个很重要的思维转换,每一条路的最优并不能保证两条路的最优,但是两条路子路径的最优却一定意味着两条路的最优,其实更单一路径求最优是一个原理。只要能想到两条路一起走这种方式,问题一下子就解决了。正向走过去和反向走回来其实并没有区别。还有一点就是优化,把五维降成三维,因为每一次只能走一步,而且只有两个方向可供选择,所以说只要步数确定了,横向或纵向的距离确定了,位置也就确定了。(注意,最后输出的不是dp【m】【m】【m】而是dp【m】【m-1】【m+n-2】原因是对不能交叉的判定,如果判定的方法和起点终点不同,或许不同)。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int m,n,mp[60][60],dp[55][55][102];//同时走两条路dp【s(步数)】【x1】【y1】【x2】【y2】
                                  // 化成三维 dp【s】【y1】【y2】(x1=s-y1,x2=s-y2) 
const int INF =(1<<30);
int ma(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    freopen("message.in","r",stdin);
    freopen("message.out","w",stdout);
    scanf("%d%d",&m,&n);
    for(int i=m;i>0;i--)
        for(int j=1;j<=n;j++)
            scanf("%d",&mp[i][j]);
    for(int i=1;i<=m;i++)
        for(int j=0;j<i;j++)
            for(int z=1;z<m+n-1;z++)
            {
                int xi=z-i+1,xj=z-j+1,yi=m-i+1,yj=m-j+1;
                if(xi<0||xj<0||xi>n||xj>n)continue;
                dp[i][j][z]=dp[i][j][z-1];
                if(j-1>0)dp[i][j][z]=ma(dp[i][j][z],ma(dp[i-1][j-1][z-1],dp[i][j-1][z-1]));
                if(i-1>j)dp[i][j][z]=ma(dp[i][j][z],dp[i-1][j][z-1]);
                dp[i][j][z]+=mp[yi][xi]+mp[yj][xj];
            }
    int ans=dp[m][m-1][m+n-2];
    printf("%d",ans);
    return 0;
}
  1. 双栈排序
    (twostack.pas/c/cpp)
    【问题描述】
    Tom最近在研究一个有趣的排序问题。如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序。
    操作a
    如果输入序列不为空,将第一个元素压入栈S1
    操作b
    如果栈S1不为空,将S1栈顶元素弹出至输出序列
    操作c
    如果输入序列不为空,将第一个元素压入栈S2
    操作d
    如果栈S2不为空,将S2栈顶元素弹出至输出序列
    如果一个1~n的排列P可以通过一系列操作使得输出序列为1, 2,…,(n-1), n,Tom就称P是一个“可双栈排序排列”。例如(1, 3, 2, 4)就是一个“可双栈排序序列”,而(2, 3, 4, 1)不是。下图描述了一个将(1, 3, 2, 4)排序的操作序列:a, c, c, b, a, d, d, b
    这里写图片描述
    当然,这样的操作序列有可能有几个,对于上例(1, 3, 2, 4),a, c, c, b, a, d, d, b是另外一个可行的操作序列。Tom希望知道其中字典序最小的操作序列是什么。
    【输入】
    输入文件twostack.in的第一行是一个整数n。
    第二行有n个用空格隔开的正整数,构成一个1~n的排列。
    【输出】
    输出文件twostack.out共一行,如果输入的排列不是“可双栈排序排列”,输出数字0;否则输出字典序最小的操作序列,每两个操作之间用空格隔开,行尾没有空格。
    【输入输出样例1】
    twostack.in twostack.out
    4
    1 3 2 4 ………a b a a b b a b
    【输入输出样例2】
    twostack.in twostack.out
    4
    2 3 4 1 ……..0
    【输入输出样例3】
    twostack.in twostack.out
    3
    2 3 1 ………..a c a b b d

【限制】
30%的数据满足: n<=10
50%的数据满足: n<=50
100%的数据满足: n<=1000
第四题
最后一道是一个二分图染色问题,最容易想到的方法就是打一个模拟,应该可以过三个点,这道题最重要的一点就是要想清楚为什么纯模拟不行,下面提供一组测试数据 10 10 2 3 1 7 9 8 4 5 6 可以说明这个问题,这里提供的方法是数组模拟,当然直接用栈也完全没问题。既然直接模拟不行那么又怎么来操作呢?方法就是在模拟之前就做一个判断,哪些数是至始至终都不能放在一起的,所以就是一个二分图染色了。这个判断条件就是,后进入的数比先进入的数大,并且先进入的数无法出栈(可以结合下面的代码理解)。有了一个颜色,再去模拟就没有多大问题了。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

int a[1001], f[1001], la[1001], lb[1001], co[1001], head[1001], pa, pb, n, idcc;
//co[]栈编号,la[]栈1,lb[]栈二,pa,pb头指针 

struct ln{
    int to, nxt;
    ln(){
        to = nxt = 0;
    }
    void adde( int a, int b ){
        to = a, nxt = b;
    }
}ed[2005];

void adde( int u, int v ){
    ed[++idcc].adde(u, head[v]);
    head[v] = idcc;
    ed[++idcc].adde(v, head[u]);
    head[u] = idcc;
}

void dfs( int u, int c ){
    co[u] = c;
    for( int i = head[u]; i; i = ed[i].nxt ){
        int v = ed[i].to;
        if(co[v] == c){
            printf("0\n");
            exit(0);//退出主程序 
        }
        if(!co[v])
          dfs(v, 3-c);//1,2两个栈 
    }
}

int main()  {
    freopen("twostack.in", "r", stdin);
    freopen("twostack.out", "w", stdout);
    scanf("%d", &n);
    for(int i=1; i<=n; i++)  
        scanf("%d", &a[i]);
    f[n + 1] = 0x3f3f3f3f;
    for(int i=n; i>=1; i--) //从后往前 
        f[i] = min(f[i+1], a[i]);//找到一个比当前数小的即可,所以只保存最小值 
    for(int i=1; i<n; i++)
        for(int j=i+1; j<=n; j++)
            if( a[i] < a[j] && f[j + 1] < a[i]){//i在j前且i比j小,且i不能弹出时 
                adde(i, j);
            }//i,j不能在同一个栈中 
    for(int i=1; i<=n; i++)
        if(!co[i])  
            dfs(i, 1);
    int idc = 1;//当前需要输出的值 
    for(int i=1; i<=n; i++){
        if(co[i] == 1){
            la[++pa] = a[i];
            printf("a ");
        }
        else{
            lb[++pb] = a[i];
            printf("c ");
        }
        while(pa && la[pa] == idc || pb && lb[pb] == idc){
            if(pa && la[pa] == idc){
                pa--;
                printf("b ");
            }
            else{
                pb--;
                printf("d ");
            }
            idc++;
        }
    }
    return 0;  
}

考试总结
一次08年noip真题练习。第一道题比较简单,做法也很多,这一次我是用map做的,因为本身对STL不太熟悉,所以就想尝试一下。还好,没有爆掉。第二题因为数据规模很小,所以可以直接枚举,1000*1000就ok了。不幸的是当时在推算范围的时候不准确,导致不敢放开手用暴力。明明10min完全可以解决的问题,又冥思苦想了好久,用一个特别麻烦的办法优化,最后还爆零了!所以说还是应该在题目的分析上多下点功夫,避免这种费力不讨好的事情发生。更精细化的思考问题,验证方法,确认无误之后再动键盘就可以行云流水一气呵成了。第三题是一个dp的题目,考试的时候思想没有转过来,还总是想着要怎么样实现才能让他去了之后再回来,其实两条路一起走一下子就解决了。还有就是时空的优化,把五维降成三维,其实不一定非要一下子就想到最优的解法,可以尝试着把最好想的方法写出来,然后再通过对这个方式的模拟,寻找dp各元素之间的关系,如此一来,降次的方式就不难想到了。最后一道是二分图染色问题,我只是打了一个模拟,过了三个点,直到后来还纠结了好久,一直没有想清楚为什么纯模拟不行,最后找了组测试数据 10 10 2 3 1 7 9 8 4 5 6 才发现问题,这就说明对题目的考虑还不够周全,举反例的能力还不够。所以还应该更严谨的验证方法的正确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值