NOIP2010提升組真題部分整理(沒有關押罪犯)

\(NOIP2010\)提升組真題部分整理

\(T1\)機器翻譯:

洛谷\(P1540\)ios

題目背景:

​ 小晨的電腦上安裝了一個機器翻譯軟件,他常常用這個軟件來翻譯英語文章。git

題目描述:

​ 這個翻譯軟件的原理很簡單,它只是從頭至尾,依次將每一個英文單詞用對應的中文含義來替換。對於每一個英文單詞,軟件會先在內存中查找這個單詞的中文含義,若是內存中有,軟件就會用它進行翻譯;若是內存中沒有,軟件就會在外存中的詞典內查找,查出單詞的中文含義而後翻譯,並將這個單詞和譯義放入內存,以備後續的查找和翻譯。數組

假設內存中有\(M\)個單元,每單元能存放一個單詞和譯義。每當軟件將一個新單詞存入內存前,若是當前內存中已存入的單詞數不超過\(M-1\),軟件會將新單詞存入一個未使用的內存單元;若內存中已存入\(M\)個單詞,軟件會清空最先進入內存的那個單詞,騰出單元來,存放新單詞。函數

假設一篇英語文章的長度爲\(N\)個單詞。給定這篇待譯文章,翻譯軟件須要去外存查找多少次詞典?假設在翻譯開始前,內存中沒有任何單詞。測試

輸入輸出格式:

輸入格式:

\(2\)行。每行中兩個數之間用一個空格隔開。優化

第一行爲兩個正整數\(M,N\),表明內存容量和文章的長度。spa

第二行爲\(N\)個非負整數,按照文章的順序,每一個數(大小不超過\(1000\))表明一個英文單詞。文章中兩個單詞是同一個單詞,當且僅當它們對應的非負整數相同。翻譯

輸出格式:

一個整數,爲軟件須要查詞典的次數。code

輸入輸出樣例:

輸入樣例:

3 7
1 2 1 5 4 4 1

輸出樣例:

5

說明:

每一個測試點\(1s\)

對於\(10\%\)的數據有\(M=1,N≤5\)

對於\(100\%\)的數據有\(0≤M≤100,0≤N≤1000\)

整個查字典過程以下:每行表示一個單詞的翻譯,冒號前爲本次翻譯後的內存情況:

空:內存初始狀態爲空。

$1. $\(1\):查找單詞1並調入內存。

\(2.\) \(1 2\):查找單詞\(2\)並調入內存。

\(3.\) \(12\):在內存中找到單詞\(1\)

\(4.\) \(1 2 5\):查找單詞\(5\)並調入內存。

$5. $\(2 5 4\):查找單詞\(4\)並調入內存替代單詞\(1\)

\(6.\)\(2 5 4\):在內存中找到單詞\(4\)

\(7.\)\(5 4 1\):查找單詞\(1\)並調入內存替代單詞\(2\)

共計查了\(5\)次詞典。

題解:

​ 這道題應該是一道典型的隊列題目。用隊列來模擬翻譯軟件內存的調用與存儲狀況,若是有當前單詞,就繼續,若是沒有,就將當前單詞入隊。而後根據隊列中的元素個數來判斷是否要將隊尾元素出隊。

​ 這道題作完了,我只想總結一下隊列的\(STL\)函數:

\(queue<int>q:\) 定義一個\(int\)類型的隊列\(p\)

\(q.push(x):\)將元素\(x\)放入隊頭

\(q.pop():\)彈出隊尾元素

\(q.front():\)這個函數的返回值爲隊頭的元素

\(q.back():\)這個函數的返回值爲隊尾的元素

\(q.empty():\)判斷隊列是否爲空

\(q.size():\)返回隊列中的元素個數

代碼:

#include<cstdio>
#include<iostream>
#include<queue> //隊列的STL函數所必備的頭文件

using namespace std;

inline int read()//讀入優化(就是傳說中的快讀)
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') F=-1; c=getchar();}
    while(isdigit(c)){num=num*10+c-'0'; c=getchar();}
    return num*F;
}

queue<int>q;//定義隊列q

bool a[1500];//用a[i]來判斷單詞i是否在隊列中出現過
int n,m;
int ans=0;//查詢單詞的次數

int main()
{
    m=read(),n=read();
    for(int i=1;i<=n;++i)//循環讀入單詞
    {
        int x=read();
        if(a[x]==true)  continue;//若是隊列中有該單詞,就繼續讀入
        else if(q.size()<m)//若是隊列中的元素不超過m個,即內存沒有滿
        {
            q.push(x);//將這個單詞加入隊列(加入內存)
            ans++;//從外存查詢單詞的次數加一
            a[x]=true;//如今隊列中有了該元素,即內存中有了該單詞
        }
        else if(q.size()==m)//若是隊列已滿,即內存炸了
        {
            a[q.front()]=false;//將隊頭元素彈出以前要將該元素標爲「沒有在隊列中出現」
            q.pop();//彈出隊頭元素
            q.push(x);//將當前元素入隊
            ans++;//又在外存中查詢了一次詞典
            a[x]=true;//當前的元素標記爲已出現
        }
    }
    printf("%d",ans);//直接輸出查詞典的數量就結束了
    return 0;
}

\(T2\)烏龜棋

洛谷\(P1541\)

題目背景:

小明過生日的時候,爸爸送給他一副烏龜棋看成禮物。

題目描述:

烏龜棋的棋盤是一行\(N\)個格子,每一個格子上一個分數(非負整數)。棋盤第\(1\)格是惟一的起點,第\(N\)格是終點,遊戲要求玩家控制一個烏龜棋子從起點出發走到終點。

烏龜棋中\(M\)張爬行卡片,分紅\(4\)種不一樣的類型(\(M\)張卡片中不必定包含全部\(4\)種類型的卡片,見樣例),每種類型的卡片上分別標有\(1,2,3,4\)四個數字之一,表示使用這種卡片後,烏龜棋子將向前爬行相應的格子數。遊戲中,玩家每次須要從全部的爬行卡片中選擇一張以前沒有使用過的爬行卡片,控制烏龜棋子前進相應的格子數,每張卡片只能使用一次。

遊戲中,烏龜棋子自動得到起點格子的分數,而且在後續的爬行中每到達一個格子,就獲得該格子相應的分數。玩家最終遊戲得分就是烏龜棋子從起點到終點過程當中到過的全部格子的分數總和。

很明顯,用不一樣的爬行卡片使用順序會使得最終遊戲的得分不一樣,小明想要找到一種卡片使用順序使得最終遊戲得分最多。

如今,告訴你棋盤上每一個格子的分數和全部的爬行卡片,你能告訴小明,他最多能獲得多少分嗎?

輸入輸出格式:

輸入格式:

每行中兩個數之間用一個空格隔開。

\(1\)\(2\)個正整數\(N,M\),分別表示棋盤格子數和爬行卡片數。

\(2\)\(N\)個非負整數,\(a_1,a_2,…,a_N\),其中\(a_i\)表示棋盤第\(i\)個格子上的分數。

\(3\)\(M\)個整數,\(b_1,b_2,…,b_M\),表示\(M\)張爬行卡片上的數字。

輸入數據保證到達終點時恰好用光\(M\)張爬行卡片。

輸出格式:

\(1\)個整數,表示小明最多能獲得的分數。

輸入輸出樣例:

輸入樣例:

9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1

輸出樣例:

73

說明:

每一個測試點\(1s\)

小明使用爬行卡片順序爲\(1,1,3,1,2\),獲得的分數爲\(6+10+14+8+18+17=73\)。注意,因爲起點是\(1\),因此自動得到第\(1\)格的分數\(6\)

對於\(30\%\)的數據有\(1≤N≤30,1≤M≤12\)

對於\(50\%\)的數據有\(1≤N≤120,1≤M≤50\),且\(4\)種爬行卡片,每種卡片的張數不會超過\(20\)

對於\(100\%\)的數據有\(1≤N≤350,1≤M≤120\),且\(4\)種爬行卡片,每種卡片的張數不會超過\(40\)\(0≤a_i≤100,1≤i≤N,1≤b_i≤4,1≤i≤M\)

題解:

\(30\)分題解:

​ 這道題我剛開始想到的是深度優先搜索(主要是最近作的搜索題太多了),可是這種方法只能獲得\(30\)分,緣由就是數據太毒,若是用搜索的話,複雜度應該是\(O(2^n)\)的,根本不能對其有任何奢望。可是代碼仍是能夠看看的:

\(30\)分代碼:

#include<cstdio>
#include<iostream>
#include<algorithm> 

using namespace std;

inline int read()//讀入優化
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') F=-1; c=getchar();}
    while(isdigit(c)){num=num*10+c-'0'; c=getchar();}
    return F*num; 
}

int pos[5]={0,1,2,3,4};//這個數組存儲當前出的卡片種類 
int n,m;
int qi_pan[400];//棋盤上的對應得分
int ka_pian[150];//對應卡片的種類
int ans,sum;//ans是答案,sum是當前搜索的得分(將這兩個值進行比較,就能得出最大值)
int num[5];//num[i]表示卡片類型爲i的卡片數量

void dfs(int k,int d)//dfs(k,d)表示當前位置是k,已經用了d張卡牌
{
    if(k==n)//若是當前位置已經到了終點
    {
        ans=max(ans,sum);//比較最大值
        return ;//回溯
    }
    if(d==m)//若是當前已經用了全部的卡牌(由於題目保證用完全部卡牌後正好走到終點,因此這兩個判斷都是同樣的)
    {
        ans=max(ans,sum);//比較最大值
        return ;//回溯
    }
    else//若是沒有到達終點
    {
        for(int i=1;i<=4;++i)//調用四種前進方式
        {
            if(num[pos[i]]>0)//若是當前前進方式仍有卡牌
            {
                int r=k+pos[i];//定義r爲接下來搜索的起點
                num[pos[i]]--;//減小一張卡牌
                sum+=qi_pan[r];//已經獲得的分加上當前的得分
                dfs(r,d++);//繼續搜索
                sum-=qi_pan[r];
                d--;
                num[pos[i]]++;//回溯,將三個被更改過的變量進行還原
            }
            else//若是沒有卡牌可使用
                continue;//看看下一張有沒有用過(由於總會有沒有用過的卡片的)
        }
    }
}

int main()
{
    n=read(),m=read();//讀入n和m
    for(int i=1;i<=n;++i)   qi_pan[i]=read();//讀入這個棋盤
    for(int i=1;i<=m;++i)
    {
        ka_pian[i]=read();//讀入每一個卡片的種類
        num[ka_pian[i]]++;//處理num數組
    }
    dfs(1,0);//從第一個位置開始搜索,一張卡片都沒有用
    printf("%d",ans+qi_pan[1]);//輸出時別忘了加上第一格所加的分數
    return 0;//而後咱們就愉快地獲得30分啦!
}

\(100\)分題解:

​ 搜索會炸,那麼咱們應該如何優化呢?

​ 答案就是\(dp\)

​ 可是咱們如何設狀態呢?

​ 接下來提供一種思路:

\(dp[a][b][c][d]\)表示第\(1\)種卡片用了\(a\)張,第\(2\)種卡片用了\(b\)張,第\(3\)種卡片用了\(c\)張,第\(4\)種卡片用了\(d\)張時的最大得分。

這樣咱們就能夠以\(a\)(第\(1\)張卡片)爲例推導一下狀態轉移方程:

\(if(num[a]>0)\space\space dp[a][b][c][d]=max(dp[a][b][c][d],dp[a-1][b][c][d]+qipan[now])\)

\(now=1+a+2*b+3*c+4*d\)

其他的方程你們能夠本身推一下,都是同樣的。

\(100\)分代碼:

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

inline int read()//如出一轍的快讀
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') F=-1; c=getchar();}
    while(isdigit(c)){num=num*10+c-'0'; c=getchar();}
    return num*F;
}

int n,m;
int qi_pan[400];//定義同上
int num[5];//定義同上
int dp[45][45][45][45];//4維dp的每一維都要在40位以上,不然就會RE

int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;++i)   qi_pan[i]=read();
    for(int i=1;i<=m;++i)
    {
        int x=read();
        num[x]++;
    }
    for(int i=0;i<=num[1];++i)
        for(int j=0;j<=num[2];++j)
            for(int k=0;k<=num[3];++k)
                for(int l=0;l<=num[4];++l)
                {
                    int now=1+1*i+2*j+3*k+4*l;//計算當前的得分
                    if(i)   dp[i][j][k][l]=max(dp[i][j][k][l],dp[i-1][j][k][l]+qi_pan[now]);
                    if(j)   dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j-1][k][l]+qi_pan[now]);
                    if(k)   dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k-1][l]+qi_pan[now]);
                    if(l)   dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k][l-1]+qi_pan[now]);//狀態轉移方程
                }
    printf("%d",dp[num[1]][num[2]][num[3]][num[4]]+qi_pan[1]);//咱們要的答案就是全部卡牌都用上後的最大得分加上第一個位置對應的分數
    return 0;//AC了
}

\(T4\)引水入城:

\(T3\)關押罪犯博主暫時不會,就先咕咕咕了)

洛谷\(P1514\)

題目描述:

在一個遙遠的國度,一側是風景秀美的湖泊,另外一側則是漫無邊際的沙漠。該國的行政區劃十分特殊,恰好構成一個\(N\)\(\times M\) 列的矩形,如上圖所示,其中每一個格子都表明一座城市,每座城市都有一個海拔高度。

img

爲了使居民們都儘量飲用到清澈的湖水,如今要在某些城市建造水利設施。水利設施有兩種,分別爲蓄水廠和輸水站。蓄水廠的功能是利用水泵將湖泊中的水抽取到所在城市的蓄水池中。

所以,只有與湖泊毗鄰的第\(1\)行的城市能夠建造蓄水廠。而輸水站的功能則是經過輸水管線利用高度落差,將湖水從高處向低處輸送。故一座城市能建造輸水站的前提,是存在比它海拔更高且擁有公共邊的相鄰城市,已經建有水利設施。因爲第\(N\) 行的城市靠近沙漠,是該國的乾旱區,因此要求其中的每座城市都建有水利設施。那麼,這個要求可否知足呢?若是能,請計算最少建造幾個蓄水廠;若是不能,求乾旱區中不可能建有水利設施的城市數目。

輸入輸出格式:

輸入格式:

每行兩個數,之間用一個空格隔開。輸入的第一行是兩個正整數\(N,M\),表示矩形的規模。接下來\(N\) 行,每行\(M\) 個正整數,依次表明每座城市的海拔高度。

輸出格式:

兩行。若是能知足要求,輸出的第一行是整數\(1\),第二行是一個整數,表明最少建造幾個蓄水廠;若是不能知足要求,輸出的第一行是整數\(0\),第二行是一個整數,表明有幾座乾旱區中的城市不可能建有水利設施。

輸入輸出樣例:

輸入樣例\(1\)

2 5
9 1 5 4 3
8 7 6 1 2

輸出樣例\(1\)

1
1

輸入樣例\(2\)

3 6
8 4 5 6 4 4
7 3 4 3 3 3
3 2 2 1 1 2

輸出樣例\(2\)

1
3

說明:

樣例\(1\)說明:

只須要在海拔爲\(9\)的那座城市中建造蓄水廠,便可知足要求。

樣例\(2\)說明:

img

上圖中,在\(3\)個粗線框出的城市中建造蓄水廠,能夠知足要求。以這\(3\)個蓄水廠爲源頭在乾旱區中建造的輸水站分別用\(3\) 種顏色標出。固然,建造方法可能不惟一。

數據範圍:

img

題解:

​ 剛拿到題時,本人表示一臉懵逼。我$@%@#%@#@#,這@%@#%&^什麼!@#^@^!題啊???

解決方法固然是問大佬啊!\(\Omega\omega\Omega\)

​ 首先咱們考慮一個問題:

若是將湖邊的某一個城市做爲蓄水廠,那麼這個城市所對應的下游沙漠邊緣的城市必定是一條連續的序列

怎麼證實呢???

反證法:(摘自洛谷中某位大佬的題解

假設存在一種狀況使得覆蓋狀況以下, 覆蓋不連續: img

假設藍色覆蓋路線以下: img

由於右邊紅色被覆蓋了, 因此從紅色水庫到下方必然有一條路徑 img

發現路徑必有交(紫色部分), 因此紅色水庫的水也會流入藍色那部分, 假設不成立

這樣的話,咱們能夠跑一個搜索,處理出第一行每個城市所對應的最後一行區間左右端點。而後跑一邊貪心,處理區間覆蓋問題就能夠了

代碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring> 

using namespace std; 

inline int read()//又是這個快讀
{
    int F=1,num=0;
    char c=getchar();
    while(!isdigit(c)){if(c=='-') F=-1; c=getchar();}
    while(isdigit(c)){num=num*10+c-'0'; c=getchar();}
    return num*F;
}

int dx[5]={0,1,0,-1,0},dy[5]={0,0,1,0,-1};//存儲搜索時的4個方向
int l[550][550],r[550][550];//l[x][y]和r[x][y]存儲對應座標(x,y)向下的左右區間端點
bool vis[550][550];//判斷座標是否被訪問過
int map[550][550];//存圖
int n,m;

void dfs(int x,int y)//搜索點(x,y)
{
    vis[x][y]=true;//先標記爲搜過
    for(int i=1;i<=4;++i)//四個方向
    {
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<1||ny<1||nx>n||ny>m)  continue;//若是出界了,就繼續下一個方向
        if(map[nx][ny]>=map[x][y])  continue;//若是不能流下,就繼續下一個方向
        if(!vis[nx][ny])    dfs(nx,ny);//若是當前點沒有被搜索過,就搜索
        l[x][y]=min(l[x][y],l[nx][ny]);//當前點的左端點爲該點與下一個點的左端點的最小值(最靠左)
        r[x][y]=max(r[x][y],r[nx][ny]);//當前點的右端點爲該點與下一個點的右端點的最大值(最靠右)
    }//其實這是一個遞歸過程,程序先找到最深的一層,而後用這一層的數值來更新前面幾層的數值
}

int main()
{
    n=read(),m=read();
    memset(l,0x3f,sizeof(l));//清空l數組
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            map[i][j]=read();
    for(int i=1;i<=m;++i)   l[n][i]=r[n][i]=i;//初始化,令最後一排的每一個城市的左端點和右端點都爲本身
    for(int i=1;i<=m;++i)   if(!vis[1][i])  dfs(1,i);//若是第一行的某城市尚未被搜索過,就搜索這個城市
    int how=0;//how是用來記錄解的個數的
    for(int i=1;i<=m;++i)   if(!vis[n][i])  ++how;//從左往右掃一遍,若是有沒有訪問過的點,how+1
    if(how!=0)//若是有未訪問過的點
    {
        printf("0\n%d",how);//輸出未訪問過的點的個數,即沒有水的城市的個數
        return 0;//直接結束程序
    }
    int lft=1;//這個是用來存儲當前的左端點的
    while(lft<=m)//若是沒有徹底覆蓋
    {
        int maxn=0;//實際上是表明了右端點的最值
        for(int i=1;i<=m;++i)   if(l[1][i]<=lft)    maxn=max(maxn,r[1][i]);//掃描一遍第一排的左端點,而且進行比較,來更新maxn的值,區間覆蓋問題的思路
        how++;//每選一個就將答案加一
        lft=maxn+1;//而後找下一個區間
    }
    printf("1\n%d",how);//輸出
    return 0;
}
相關文章
相關標籤/搜索