NOIP2009普及组初赛难点整理

问题求解

小陈现有 2 个任务 A,B 要完成,每个任务分别有若干步骤如下: A = a 1 − > a 2 − > a 3 A=a_1->a_2->a_3 A=a1>a2>a3 B = b 1 − > b 2 − > b 3 − > b 4 − > b 5 B=b_1->b_2->b_3->b_4->b_5 B=b1>b2>b3>b4>b5 。在任何时候,小陈只能专心做某个任务的一个步骤。但是如果愿意,他可以在做完手中任务的当前步骤后,切换至另一个任务,从上次此任务第一个未做的步骤继续。每个任务的步骤顺序不能打乱,例如 … a 2 − > b 2 − > a 3 − > b 3 … \ldots a_2->b_2->a_3->b_3 \ldots a2>b2>a3>b3是合法的,而 … a 2 − > b 3 − > a 3 − > b 2 … \ldots a_2->b_3->a_3->b_2 \ldots a2>b3>a3>b2是不合法的。小陈从 B 任务的 b 1 b_1 b1 步骤开始做,当恰做完某个任务的某个步骤后,就停工回家吃饭了。当他回来时,只记得自己已经完成了整个任务 A ,其他的都忘了。试计算小陈饭前已做的可能的任务步骤序列共有(70)种。

【解析】A任务全部完成,那么可以根据B任务的完成情况分为以下几种:

  • 完成了 b 1 b_1 b1,方案只有1
  • 完成了 b 1 , b 2 b_1,b_2 b1,b2,对A任务的序列可以分为2种情况,可以使用插空法:
    • 3个任务是连续完成的,方案有2
    • 3个任务两个是连续完成的,方案有2
  • 完成了 b 1 , b 2 , b 3 b_1,b_2,b_3 b1,b2,b3,对A任务的序列可以分为3种情况,可以使用插空法:
    • 3个任务是连续完成的,方案有 C 3 1 C_3^1 C31,共3
    • 3个任务两个是连续完成的,方案 C 3 2 × 2 C_3^2\times2 C32×2,共6
    • 3个任务是相互分隔开的,方案 C 3 3 C_3^3 C33,共1
  • 完成了 b 1 , b 2 , b 3 , b 4 b_1,b_2,b_3,b_4 b1,b2,b3,b4,对A任务的序列可以分为3种情况,可以使用插空法:
    • 3个任务是连续完成的,方案有 C 4 1 C_4^1 C41,共4
    • 3个任务两个是连续完成的,方案 C 4 2 × 2 C_4^2\times2 C42×2,共12
    • 3个任务是相互分隔开的,方案 C 4 3 C_4^3 C43,共4
  • 完成了 b 1 , b 2 , b 3 , b 4 , b 5 b_1,b_2,b_3,b_4,b_5 b1,b2,b3,b4,b5,对A任务的序列可以分为3种情况,可以使用插空法:
    • 3个任务是连续完成的,方案有 C 5 1 C_5^1 C51,共1
    • 3个任务两个是连续完成的,方案 C 5 2 × 2 C_5^2\times2 C52×2,共20
    • 3个任务是相互分隔开的,方案 C 5 3 C_5^3 C53,共10

阅读程序

T3

#include <iostream>
using namespace std;
const int c=2009;
int main()
{
    
    
    int n,p,s,i,j,t;
    cin >> n >> p;
    s=0;t=1;
    for(i=1;i<=n;i++)
    {
    
    
        t=t*p%c;
        for(j=1;j<=i;j++)
            s=(s+t)%c;
    }
    cout << s << endl;
    return 0;
}

输入:

11 2

输出:

782

【解析】计算:
( 1 × 2 1 + 2 × 2 2 + 3 × 2 3 + 4 × 2 4 + 5 × 2 5 + 6 × 2 6 + 7 × 2 7 + 8 × 2 8 + 9 × 2 9 + 10 × 2 10 + 11 × 2 11 ) m o d    2009 = 782 (1\times2^1+2\times2 ^2+3\times2^3+4\times2^4+5\times2^5+6\times2^6+7\times2^7+8\times2^8+9\times2^9+10\times2^{10}+11\times2^{11})\mod2009=782 (1×21+2×22+3×23+4×24+5×25+6×26+7×27+8×28+9×29+10×210+11×211)mod2009=782

T4

#include <iostream>
#include <cstring>
using namespace std;
const int maxn=50;
void getnext(char str[])
{
    
    
    int l=strlen(str),i,j,k,temp;
    k=l-2;
    while(k>=0&&str[k]>str[k+1]) k--;
    i=k+1;
    while(i<l&&str[i]>str[k]) i++;
    temp=str[k];
    str[k]=str[i-1];
    str[i-1]=temp;
    for(i=l-1;i>k;i--)
        for(j=k+1;j<i;j++)
            if(str[j]>str[j+1])
            {
    
    
                temp=str[j];
                str[j]=str[j+1];
                str[j+1]=temp;
            }
    return ;
}
int main()
{
    
    
    char a[maxn];
    int n;
    cin >> a >> n;
    while(n>0)
    {
    
    
        getnext(a);
        n--;
    }
    cout << a << endl;
    return 0;
}

输入:NOIP 3

输出:NPOI

【解析】模拟即可:

  • 第1次循环:a = “NOPI”
  • 第2次循环:a = “NPIO”
  • 第3次循环:a = “NPOI”

完善程序

T1(最大连续子段和)给出一个数列(元素个数不多于 100),数列元素均为负整数、正整数、0。请找出数列中的一个连续子数列,使得这个子数列中包含的所有元素之和最大,在和最大的前提下还要求该子数列包含的元素个数最多,并输出这个最大和以及该连续子数列中元素的个数。例如数列为 4,-5,3,2,4 时,输出 9 和 3;数列为 1,2,3,-5,0,7,8时,输出 16 和 7。

#include <iostream>
using namespace std;
int a[101];
int n,i,ans,len,tmp,beg;
int main(){
    
    
    cin >> n;
    for (i=1;i<=n;i++)
        cin >> a[i];
    tmp=0;
    ans=0;
    len=0;
    beg=;
    for (i=1;i<=n;i++){
    
    
        if (tmp+a[i]>ans){
    
    
            ans=tmp+a[i];
            len=i-beg;
        }
        else if (&&i-beg>len)
            len=i-beg;
        if (tmp+a[i]){
    
    
            beg=;
            tmp=0;
        }
        else;
    }
    cout << ans <<""<< len << endl;
    return 0;
}

【解析】

  • 空①,设置连续子数列的起始位置,后面代码计算长度len时使用i - beg,所以beg应该从0开始计算,此空填 0
  • 空②,打擂台如果发现更大的连续数列和,更新ans和长度len;否则,如果发现相等的连续数列和并且长度更长,则更新长度len
  • 空③、空④,如果连续数列和小于0,则从当前位置开始,重新计算连续数列和,因为对于下一项来说加上一个负数只会导致和更小。空③填<0,空④应填i
  • 空⑤,继续累加连续数列的和,此空应填tmp+=a[i]

T2 (国王放置)在 n×m 的棋盘上放置 k 个国王,要求 k 个国王互相不攻击,有多少种不同的放置方法。假设国王放置在第 (x,y) 格,国王的攻击的区域是:(x-1,y-1),(x-1,y),(x-1,y+1),(x,y-1),(x,y+1),(x+1,y-1),(x+1,y),(x+1,y+1)。读入三个数 n,m,k,输出答案。题目利用回溯法求解。棋盘行标号为 0~n-1,列标号为 0~m-1。

#include <iostream>
using namespace std;
int n,m,k,ans;
int hash[5][5];
void work(int x,int y,int tot)
{
    
    
    int i,j;
    if (tot==k)
    {
    
    
        ans++;
        return;
    }
    do
    {
    
    
        while (hash[x][y])
        {
    
    
            y++;
            if (y==m)
            {
    
    
                x++;
                y=;
            }
            if (x==n)
                return;
        }
        for (i=x-1; i<=x+1; i++)
            if (i>=0&&i<n)
                for (j=y-1; j<=y+1; j++)
                    if (j>=0&&j<m);;
        for (i=x-1; i<=x+1; i++)
            if (i>=0&&i<n)
                for (j=y-1; j<=y+1; j++)
                    if (j>=0&&j<m);
        y++;
        if (y==m)
        {
    
    
            x++;
            y=0;
        }
        if (x==n)
            return;
    }while (1);
}
int main()
{
    
    
    cin >> n >> m >> k;
    ans=0;
    memset(hash,0,sizeof(hash));;
    cout << ans << endl;
    return 0;
}

【解析】题目中通过回溯算法,深度优先搜索放置国王的方案。hash[i][j] = 0表示棋盘(i,j)位置在当前分支可以放置国王。

  • 空①,如果hash[x][y]不为0,表示此位置无法放置国王,那么沿着当前位置从左到右、从上到下找到一个可以放置国王的位置(x,y)。当走到当前行的最后一列,换到下一行第一列开始继续找。
  • 空②,将国王放置在第 (x,y) 格,国王的攻击的区域是:(x-1,y-1),(x-1,y),(x-1,y+1),(x,y-1),(x,y+1),(x+1,y-1),(x+1,y),(x+1,y+1),将这8个位置的hash值全部加1,保证沿当前分支继续搜索时不能在这些位置继续放置国王。此空应填hash[x][y]++
  • 空③,将国王放置在(x,y)位置,沿当前分支继续深度优先搜索下一个放置国王的位置,此空应填work(x, y, tot+1)
  • 空④,回溯时恢复现场,将hash值减1,此空应填hash[x][y]--
  • 空⑤,从棋盘(0,0)位置开始搜索答案,此时放置了0个国王,此空应填work(0,0,0)

猜你喜欢

转载自blog.csdn.net/qiaoxinwei/article/details/108671935