?這是第二次模擬測試的解題報告
本次題目出自OIBH模擬賽
題目如下:
===================================================================================================================
飲食問題
(eatpuz.pas/c/cpp/in/out)
時限:1 sec | 內存: 64 MB
背景
???? 平平正在減肥,所以要控制他的飲食。并且要求她及時去公園里面散步。
題目描述
平平正在減肥,所以她規定每天不能吃超過 C (10 <= C <= 35,000)卡路里的食物。農民 John 在戲弄她,在她面前放了B (1 <= B <= 21) 捅食物。每桶內都有某個單位卡路里(范圍:1..35,000)的食物(不一定相同)。平平沒有自控能力,一旦她開始吃一個桶中的食物,她就一定把這桶食物全部吃完。
平平對于組合數學不大在行。請確定一個最優組合,使得可以得到最多的卡路里,并且總量不超過C。
例如,總量上限是40卡路里, 6 桶食物分別含有7, 13, 17, 19, 29, 和 31卡路里的食物。平平可以吃7 + 31 = 38卡路里,但是可以獲取得更多: 7 + 13 + 19 = 39卡路里。沒有更好的組合了。
輸入
共兩行。
第一行,兩個用空格分開的整數: C 和 B
第二行,B個用空格分開的整數,分別表示每桶中食物所含的卡路里。
輸出
共一行,一個整數,表示平平能獲得的最大卡路里,使她不違反減肥的規則。
輸入樣例
40 6
7 13 17 19 29 31
[樣例輸出]
39
三個袋子
(bags.pas/c/cpp/in/out)
時限:1 sec | 內存: 64 MB
背景
??? 平平在公園里游玩時撿到了很多小球,而且每個球都不一樣。平平找遍了全身只發現了3個一模一樣的袋子。他打算把這些小球都裝進袋子里(袋子可以為空)。他想知道他總共有多少種放法。
題目描述
??? 將N個不同的球放到3個相同的袋子里,求放球的方案總數M。
??? 結果可能很大,我們僅要求輸出M mod K的結果。
??? 現在,平平已經統計出了N<=10的所有情況。見下表:
N?1?2?3?4?5?6?7?8?9?10
M?1?2?5?14?41?122?365?1094?3281?9842
輸入
兩個整數N,K,N表示球的個數。
輸出
輸出僅包括一行,一個整數M mod K 。
輸入樣例 ( bags.in )
11 10000
輸出樣例( bags.out )
9525
數據規模
對于 40%數據,10<=N<=10,000
對于100%數據,10<=N<=1,000,000,000
對于 100%數據,K<=100,000
智捅馬蜂窩
(clever.pas/c/cpp/in/out)
時限:1 sec | 內存: 64 MB
背景
??? 為了統計小球的方案數,平平已經累壞了。于是,他摘掉了他那800度的眼鏡,躺在樹下休息。
??? 后來,平平發現樹上有一個特別不一樣的水果,又累又餓的平平打算去把它摘下來。
題目描述
??? 現在,將大樹以一個N個節點的無向圖的形式給出,每個節點用坐標(Xi,Yi)來表示表示,平平要從第一個點爬到第N個點,除了從一個節點爬向另一個相鄰的節點以外,他還有一種移動方法,就是從一個節點跳下,到達正下方的某個節點(之間可隔著若干個點和邊),即當Xj=Xi and Yi<Yj 時,平平就可以從j節點下落到i節點,他下落所用時間滿足自由落體公式,t=sqrt((Yj-Yi)*2/g) (注意:g取10)。如果出現兩線相交的情況,我們不認為它們是相通的。
輸入
兩個整數N,V,N表示節點個數,V表示平平爬樹的速度。
接下來N行,每行包含3個整數X,Y,F,X,Y是這個點的坐標,F是他的父節點(F一定小于這個點的標號,第一行的F為0)。
注意:兩節點間距離按歐幾里德距離計算 dis = sqrt( ( x1 – x2 ) 2+ ( y1 – y2 )2 )
輸出
輸出僅包括一行,從1到N所用的最少所需時間T,保留兩位小數。
輸入樣例 ( clever.in )
9 1
5 0 0
5 5 1
6 5 2
7 6 2
6 9 2
3 6 2
4 5 2
3 2 7
7 2 3
輸出樣例 ( clever.out )
8.13
數據規模
對于100%數據,1<=N<=100,1<=V<=10,0<=X,Y<=100.
建議使用extended(pas)或double(c and c++)計算,我們對于精度造成的誤差將不予重測。
過河
(river.pas/c/cpp/in/out)
時限:1 sec | 內存: 64 MB
背景
??? 經過一番努力,平平終于摘到了水果,拿近一看才發現是馬蜂窩。
??? 平平從樹上掉了下來,沒命的跑啊跑啊……
??? 后來,他發現前方有一條河。現在他用電話求助于你,希望你在他跑到之前鋪好過河的路,讓他順利過河。
題目描述
??? 已知河的長度為L,現在我們有M個石子,我們可以把一個石子鋪在河上的一個點,讓平平踩著這些石頭過河。河上已經有N個石子,而且已知平平每步跨出的距離為S到T之間的整數( S <= T ),起點是0點,河為1到L,只要平平最后能跳過L(必須大于L)就算平平順利過河。
??? 如果平平能夠過河,請輸出最少步數,否則輸出最遠到達的距離,以便我們及時去救起平平。
輸入
第一行有五個整數L,N,M,S,T ,L是河的寬度,N表示河里原來就有N個石頭,M表示我們手上擁有的石頭,S,T表示平平的一步最小的跨度和最大跨度。接下來N行有一個值表示在河里的石頭的位置(從小到大給出)。
輸出
輸出僅包括一行,如果能夠過河,輸出最少步數,否則輸出平平能夠到達的最遠距離。
輸入樣例 ( river.in )
樣例1:
8 2 2 2 3
2
3
樣例2:
10 1 1 1 2
2
輸出樣例 ( river.out )
樣例1:
3
樣例2:
4
數據規模
對于40%數據,1<=M<=L<=1000 , 1<=S<=T<=10 ;
對于100%數據,1<=M<=L<=100000;
對于100%數據,1<=S<=T<=100,1<=N<=100.
后記
??? 可憐的平平還是沒有逃過被馬蜂蟄的命運。大老板以最快的速度把平平送到了醫院。
平平的故事就此告一段落。現在平平小朋友正躺在家里好好休息,準備沖刺2007年的NOIP……
================================================================================================================
解題報告:
第一題:
??????????? 簡單的01背包,就算使用回溯法枚舉所有可能情況也能過(經測試),數據很弱。。。。。。。
????????????本人用DP方法,求出了所有可能得到的卡路里值
????????????附上程序:
?
#include?"stdio.h"
#include?"stdlib.h"
#define?N?25
int?b[N];
int?d[N];
int?l,n;
int?ans;

void?input()


{
????int?i;
????scanf("%d%d",&l,&n);
????for(i=0;i<n;i++)
????????scanf("%d",&d[i]);
}

int?add(void)


{
????int?i,f=0;
????for(i=0;i<n;i++)

????
{
????????if(b[i])
????????????f+=d[i];
????}
????return?f;
}

int?work()


{
????int?i,j;
????int?sum;
????i=0;
????b[0]=-1;
????ans=0;
????while(i>=0)

????
{
????????if(i==n)

????????
{
????????????sum=add();
????????????if(sum>ans&&sum<=l)
????????????????ans=sum;
????????????i--;
????????????continue;
????????}
????????b[i]++;
????????if(b[i]>1)

????????
{
????????????i--;
????????????continue;
????????}
????????b[i+1]=-1;
????????i++;
????}
????return?ans;
}

int?main()


{
????freopen("eatpuz.in","r",stdin);
????freopen("eatpuz.out","w",stdout);
????input();
????printf("%d\n",work());
????return?0;
}

第二題:
????????????無奈的數學題,快速冪,或者干脆用矩陣乘法加速,看著辦吧
第三題:
????????????簡單的單元最短路,注意如果可能掉下來而不用爬的方法,更新兩點間的通路時間,其他沒什么了。
??????????? Dijkstra算法,代碼:?
#include?"stdio.h"
#include?"stdlib.h"
#include?"math.h"
#define?N?128

double?g[N][N]=
{0},v;
int?x[N],y[N];
int?n;

void?input(void)


{
????int?i,j;
????int?a,b,f;
????int?af,bf;
????double?dis;
????scanf("%d%lf",&n,&v);
????for(i=1;i<=n;i++)

????
{
???????for(j=1;j<=n;j++)
???????????g[i][j]=32768;
????}
????for(i=1;i<=n;i++)

????
{
????????scanf("%d%d%d",&a,&b,&f);
????????x[i]=a;???y[i]=b;
????????if(f==0)
????????????continue;
????????af=x[f];??bf=y[f];
????????dis=sqrt(1.0*(a-af)*(a-af)+(b-bf)*(b-bf));
????????g[i][f]=g[f][i]=dis/v;
????}
????for(i=1;i<=n;i++)

????
{
????????for(j=1;j<=n;j++)

????????
{
????????????if(x[i]==x[j]&&y[i]<y[j])

????????????
{
????????????????dis=sqrt(1.0*(y[j]-y[i])*2/10.0);
????????????????if(dis<g[j][i])
????????????????????g[j][i]=dis;
????????????}
????????}
????}
}

int?work(void)


{
????int?i,j,k;
????int?min;

????int?b[N]=
{0};
????double?len[N];
????for(i=1;i<=n;i++)

????
{
????????len[i]=g[1][i];
????}
????len[0]=32768;
????len[1]=0;
????b[1]=1;
????for(k=1;k<=n;k++)

????
{
????????min=0;
????????for(i=1;i<=n;i++)
????????????if(b[i]==0&&len[i]<len[min])
????????????????min=i;
?????????if(min==0)
????????????break;?
????????b[min]=1;
????????for(i=1;i<=n;i++)

????????
{
??????????????if(b[i]==0&&len[min]+g[min][i]<len[i])
???????????????????len[i]=len[min]+g[min][i];
????????}
????}
????printf("%.2lf\n",len[n]);
}

int?main()


{
????freopen("clever.in","r",stdin);
????freopen("clever.out","w",stdout);
????input();
????work();
????//getch();
????return?0;
}

第四題:
????????????讓人崩潰的DP題目,不過由于數據比較弱,搜索應該也是可以過的(不過本人不會)。
????????????首先我們應該明確題目中的幾個重要部分(或者說是細節步驟,要不然就是過河時的一般規律):
????????????1、如果可能到達河岸對面,那么,手中的石頭全部使用完肯定對應至少一個最優解(為什么?自己想,簡單的很);
????????????2、對于兩個石頭之間,是否存在可能到達方式的判斷條件:len/s*t>=len(len為兩石頭間的距離,下同)? (具體證明,可以形象的畫一畫);
????????????3、如果兩個點(或者說石頭),如果之間有可能的方法到達(即滿足2中的判斷條件),那么,從一個石頭到達另一個石頭所需要踩過的其他石頭(即非這兩個石頭)的數目為 (len-1)/t,并且可以保證一定有這樣子的一種到達方法。
????????????接下來,就應該介紹動態轉移方程,以及相關的預處理了。
????????????對于數據,我們首先要進行一些必要的預處理:
????????????1、對于讀入的每個石頭的位置,通過對于上述條件的判斷,如果從0點不可到達,則可以將次石頭略去不考慮;
????????????2、因為題目要求一定要過河(就是行動的總距離要大于L才行),所以就在 L+1~L+S 這S個位置上人為的構造出S個石頭,并認為這些石頭是本身存在與河面上的。
????????????3、對于上面提到的每一個石頭,應該對其預處理出一個值(我將其保存在B數組中),代表從0點到達這個石頭中間要踩過多少個石頭(這里踩過的石頭可以是已有的,可以是從手中放下的,石頭數允許是不合法的。注意:這個數組只是表示這一種可能情況,不表示一定滿足輸入數據);
????????????4、動態規劃的初始狀態 F[i][0]=b[i]+1。
????????????接下來,介紹動態規劃中狀態的表示方法:
????????????F[i][j]表示從0點到第i個石頭,中間如果踩過j個石頭,所需要的最少步數。(注意:此處是踩過,如果只是經過某個石頭而不必踩過,則這個石頭就不包含在這j個石頭中)。
????????????動態轉移方程:
????????????F[j][k+ifneed(i)]=min{F[i][k]+w[i][j]+1} (0<=k<i),其中w[i][j]表示從第i個石頭到第j個石頭中間要踩過的石頭數,ifneed表示一種判斷條件,就是用來判斷如果到達第j個石頭,是否一定要踩到第i個石頭上面,如果必要,則返回1,否則返回0。具體的判斷方法實現如下:
d=(rock[j]-rock[i]-1)/t;??//rock中存放的為第i個石頭的位置
need=b[j]-b[i]-d;????????????最后就是對結果的判斷了。
????????????第一項,應該是判斷是否存在能夠過河的方法。由于可以明顯的看出,在上述動態規劃的過程中,由于判斷了中轉石頭是否必要踩過,已經形成了一定的貪心規則。所以,在輸出時,就應該對最后s個石頭(就是自己加進去的石頭判斷),如果b[i]滿足條件,就取其中最小的;如果b[i]>m,就取F[i][b[i]-m]里最小的。(原因應該很簡單,因為上面在轉移和預處理時已經保證了得到最優)
????????????第二項,就是在第一項沒有找到過河步數是,求出最遠走到哪里了。這里就更為簡單了:1、最近可以走m*t的距離;2、如果F[i][j]可行(就是不為0),并且滿足m+j>=b[i],就判斷rock[i]+(m+j-b[i])*t 是否為可行解即可。
????????????
????????????再來分析一下時間復雜度,由于石頭數不大于100,s也不大于10,對于上述O(N^3)的算法是絕對不會超時的。這樣,這道題就完美解決了。
附上代碼:
#include?"stdio.h"
#include?"stdlib.h"
#define?N?210

int?rock[N];?
int?b[N];?

int?f[N][N]=
{0};?
int?l,n,m,s,t;

void?input()


{
????int?i;
????int?cnt;
????scanf("%d%d%d%d%d",&l,&n,&m,&s,&t);
????cnt=1;
????for(i=1;i<=n;i++)

????
{
????????scanf("%d",&rock[cnt]);
????????if(rock[cnt]/s*t>=rock[cnt])

????????
{
????????????b[cnt]=(rock[cnt]-1)/t;
????????????f[cnt][0]=b[cnt]+1;
????????????cnt++;
????????}
????}
????for(i=1;i<=s;i++)

????
{
????????rock[cnt]=l+i;
????????b[cnt]=(rock[cnt]-1)/t;
????????f[cnt][0]=b[cnt]+1;
????????cnt++;
????}
????n=cnt-1;
}

int?work(void)


{
????int?ans;
????int?i,j,k;
????int?d,need;
????
????for(i=1;i<=n;i++)

????
{
????????for(j=1;j<=n;j++)

????????
{
????????????if(?((rock[j]-rock[i])/s*t)?>=?(rock[j]-rock[i])?)

????????????
{
????????????????d=(rock[j]-rock[i]-1)/t;
????????????????need=b[j]-b[i]-d;?
????????????????for(k=0;k<i;k++)

????????????????
{
????????????????????if(f[i][k])
????????????????????????if(f[j][k+need]==0||f[j][k+need]>f[i][k]+d+1)
????????????????????????????f[j][k+need]=f[i][k]+d+1;
????????????????}
????????????}
????????}
????}
????ans=2147483647;
????for(i=n-s+1;i<=n;i++)

????
{
????????if(b[i]<=m&&ans>f[i][0])

????????
{
????????????????ans=f[i][0];
????????}
????????else?if(?(n-1>b[i]-m)?&&?(f[i][b[i]-m]>0)?&&?(ans>f[i][b[i]-m])?)

????????
{
????????????ans=f[i][b[i]-m];
????????}
????}?
????if(ans==2147483647)

????
{
????????ans=m*t;
????????for(i=1;i<=n;i++)

????????
{
????????????for(j=0;j<i;j++)

????????????
{
????????????????if(f[i][j]&&m+j>=b[i])
????????????????????if(rock[i]+(m+j-b[i])*t>ans)
????????????????????????ans=rock[i]+(m+j-b[i])*t;
????????????}
????????}
????}?
????return?ans;
}

int?main()


{
????freopen("river.in","r",stdin);
????freopen("river.out","w",stdout);
????input();
????printf("%d\n",work());
????return?0;
}
