这是第二次模拟测试的解题报告
本次题目出自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;
}