DP-DAY3游记
问题 A: 2017夏令营第一阶段(Day3)问题A拆分数字I
题目描述
例如:N=4,有1+1+1+1、1+1+2、1+2+1、1+3、2+1+1、2+2、3+1、4八种方法。
输入
输出
样例输入
3
样例输出
4
第一题是一道典型的找子问题的题目,设f[i]表示N=i情况下的方案数,我们拿题目中N=4的情况讲述。
我们可以把他得到的答案(1+1+1+1、1+1+2、1+2+1、1+3、2+1+1、2+2、3+1、4)这堆东西分一下类别。
以1开头的有:1+1+1+1,1+1+2,1+2+1,1+3
以2开头的有:2+1+1,2+2,
以3开头的有:3+1
以4开头的有:4
我们不难发现,(以1开头为例),实际上就是f[3],因为开头1后面的数字的总和恰好为3,所以方案数自然为f[3]
以此类推,以2开头的方案数为f[2],以3开头的方案数为f[1],以4开头的为......
所以f[4]=f[3]+f[2]+f[1],那么f[3]=f[2]+f[1],我们发现,其实f[4]=2*f[3],所以以此类推f[3]=2*f[2]
我们以此找到规律f[n]=f[n-1]*2,(妙啊~
f[0]要初始化为1,qwq
来,上代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long n,ans; //看着数据大小,无奈地打起了long long(qwq 4 int main(){ 5 scanf("%lld",&n); 6 ans=pow(2,n-1); //运用f[n]=f[n-1]*2的规律 7 printf("%lld",ans); 8 return 0; 9 }
完美!
问题 B: 2017夏令营第一阶段(Day3)问题B方格最短路径
题目描述
输入
下面n行,每行m个整数,每个数范围在[1, 100] 。
输出
样例输入
3 4
3 2 3 7
2 1 5 1
3 2 1 6
样例输出
15
这一道题也是一道经典子问题的题目。
这道题要求的值是从左上角到达右下角点(每到达一个点加上那个点的值)的值最小值。
设这道题的f[i][j]表示到点(i,j)时得到的最优值,那么我们最终要求的目标就是f[n][m]。
题目中要求只能走右边和下边,所以点f[n][m]只能从f[n-1][m]和f[n][m-1]两个点过来因此此时我们需要求出f[n-1][m]和f[n][m-1]两个点的最优值,以此内推,我们首先要求的是f[1][1]的最优值,然后往别的点便利最优值,从而得出状态转移方程,f[i][j]=min(f[i-1][j],f[i][j-1])+a[i][j]
妙啊~
上代码!
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 int a[1005][1005]; 5 int f[1005][1005]; 6 const int oo=0x7f7f7f; //最大值 7 int main(){ 8 scanf("%d%d",&n,&m); 9 for(int i=1;i<=n;i++) 10 for(int j=1;j<=m;j++) 11 scanf("%d",&a[i][j]); 12 for(int i=0;i<=n+1;i++) 13 for(int j=0;j<=m+1;j++) 14 f[i][j]=oo; //这里需要初始化一下 15 f[0][1]=f[1][0]=0; 16 for(int i=1;i<=n;i++) 17 for(int j=1;j<=m;j++){ 18 f[i][j]=min(f[i][j-1],f[i-1][j])+a[i][j]; 19 } 20 printf("%d",f[n][m]); 21 return 0; 22 }
完美!
问题 C: 2017夏令营第一阶段(Day3)问题C最长上升序列
题目描述
输入
第二行有N个整数,每个数范围在[1, 1000000] 。
输出
样例输入
8 9 1 3 7 4 1 5 5
样例输出
4
如果去过洛古的大佬,应该知道有一道题叫做导弹拦截,那道题跟这道题及其相似。
这道题设f[i]为当找到第i个数字时的最优值。
那么我们可以枚举前面i前面的数值然后如果找到是满足递增情况的时候就可以加多一种情况,因此方程就是if(a[j]>a[i])f[i]=max(f[i],f[j]+1);
上代码!
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,f[10005]; 4 int a[10005]; 5 const int oo=0x7f7f7f; 6 int mmax; 7 int main(){ 8 scanf("%d",&n); 9 a[0]=-oo; 10 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 11 for(int i=n;i>=1;i--){ 12 f[i]=1; 13 for(int j=i+1;j<=n;j++) 14 if(a[j]>a[i])f[i]=max(f[i],f[j]+1); 15 mmax=max(f[i],mmax); //统计最优值 16 } 17 printf("%d",mmax); 18 return 0; 19 }
完美!
问题 D: 2017夏令营第一阶段(Day3)问题D拆分数字II
题目描述
把数字N拆分一些正整数的和,问有多少种本质不同的方法?
例如:N=4,有1+1+1+1、1+1+2、1+3、2+2、4五种方法。
提示:1+3和3+1是本质相同的方法。
输入
第一行:一个整数N,范围在[1,50]。
输出
输出方案数。
样例输入
3
样例输出
3
从题面分析本题与第一题类似但又不同,因为以数学的思想讲第一题是排列,而本题为组合。
我们不妨把他想象成完全背包。
以题目中的例子“N=4,有1+1+1+1、1+1+2、1+3、2+2、4”,我把得到组合的第一个数字称为num[i][1](num[1][1]=1,num[1][2]=1,num[1][3]=1,num[1][4]=1……num[4][1]=4)
那么num[i][j]可以放1~N的数字,然后按照完全背包的过程就是每个数字(1~N)你可以取无限次,只要满足num[i][1]+num[i][u]总和为N就行了。
所以我们可以用完全背包的思路解决这道题。
上代码~!
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long n,f[10005]; 4 int main(){ 5 scanf("%lld",&n); 6 f[0]=1; //千万别少了初始化 7 for(long long i=1;i<=n;i++) //完全背包 8 for(long long j=i;j<=n;j++) 9 f[j]+=f[j-i]; 10 printf("%lld",f[n]); 11 return 0; 12 }
完美!
问题 E: 2017夏令营第一阶段(Day3)问题E :砖块(brick)
题目描述
现有一种形状为1*2的砖块,要用这种砖块摆成宽为2,长为n的墙,问有多少种方案?
输入
该题有若干组测试数据,每组数据一行,一行一个正整数n(n<=1000000)表示长,当n=0时结束。
输出
对应每组的方案数(模1000000007)。
样例输入
1 2 3 0
样例输出
1 2 3
提示
这道题跟一道叫做“台阶问题”的题目很相似。(也就是说你可以双倍经验
我们不妨把1*2的方块竖着放定义为走1步,而横着放定义为走2步。
那么问题就被我们转换成一个新问题:一个n阶的台阶,你可以走1步或者走2步,最后一步必须走到终点,问有多少种走法。
(然而台阶问题可以走1~K步,也就是说这道题比台阶问题还简单。
我们设f[i]为走到第i个位置的时候的方案总数,那么我们想要求的答案就是f[n]。
那么我们怎么得到f[n]呢?显然可以走1步或者2步,我们就可以从f[n-1]和f[n-2]两个格子过来。
又因为加法原理,我们得到公式:f[n]=f[n-1]+f[n-2],以此类推,f[i]=f[i-1]+f[i-2]。
这不就是著名的肥波纳妾数列吗,呸(斐波那契数列【手动滑稽】
好了不多说,上代码!
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long n=1,k,a[1000005]; 4 const long long mod=1000000007; //别忘了mod 5 int main(){ 6 while(n!=0){ 7 memset(a,0,sizeof(a)); 8 scanf("%lld",&n); 9 if(n==0)return 0; 10 a[0]=1; 11 for(long long i=1;i<=n;i++) 12 a[i]=a[i-1]+a[i-2],a[i]%=mod; //肥波纳妾啦( 13 printf("%lld\n",a[n]); 14 } 15 return 0; 16 }
完美~!
问题 F: 2017夏令营第一阶段(Day3)问题F :合唱队形
题目描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1 < …Ti+1 > … > TK(1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入
第一行是一个整数N(2 <= N <= 200),表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。
输出
包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
样例输入
8 186 186 150 200 160 130 197 220
样例输出
4
其实这道题我什么也不想说,其实就是第三题的升级版,你只需要两边都dp一遍就好了,上代码~!(没看懂得朋友可以去琢磨第三题
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,a[10005],ans,zans[10005],zans1[10005]; 4 int main(){ 5 scanf("%d",&n); 6 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 7 for(int i=1;i<=n;i++) 8 for(int j=1;j<=i;j++) 9 if(a[i]>a[j])zans[i]=max(zans[i],zans[j]+1); //像第三题一样dp一遍左边(从左往右 10 for(int i=n;i>=1;i--) 11 for(int j=n;j>=i;j--) 12 if(a[i]>a[j])zans1[i]=max(zans1[i],zans1[j]+1); //像第三题一样dp一遍右边(从右往左 13 for(int i=1;i<=n;i++) 14 ans=max(ans,zans[i]+zans1[i]+1); //相加便利得结果 15 printf("%d\n",n-ans); //剩下的就是需要移除的人的个数 16 return 0; 17 }
问题 G: 2017夏令营第一阶段(Day3)问题G :维修栅栏(fence)
题目描述
农场的栅栏年久失修,出现了多处破损,晶晶准备维修它,栅栏是由n块木板组成的,每块木板可能已经损坏也可能没有损坏。晶晶知道,维修连续m个木板(这m个木板不一定都是损坏的)的费用是sqrt(m)。可是,怎样设计方案才能使总费用最低呢?请你也来帮帮忙
输入
第一行包含一个整数n(n≤2500),表示栅栏的长度;
第二行包含n个由空格分开的整数。如果第i个数字是0,则表示第i块木板已经损坏,否则表示没有损坏。
输出
仅包含一个实数,表示最小维修费用;注意:答案是小数,最少精确到0.001。
样例输入
9 0 –1 0 1 2 3 0 –2 0
样例输出
3.000
这一道题非常经典,相对于前几道题有点难度,嗯。
首先来理解一下题目,有些朋友会想欸,我一个个修他不香吗,为什么那么麻烦。
如果你这样理解你就错了,我们手动水一组样例:“0,1,0”
那么如果我们一个个修我们就要花费√(1)+√(1)=2的费用
但是你有没有想过√(3)=1.732050807568877……,而且<√(1)+√(1)
所以这就是我们要研究的问题。
我们设f[i]为修到第i个围栏时得到的最小费用。
那么我们要求得自然就是f[n]。
那么f[n]怎么求呢?
我们假设第i个是数字“0”。
那么f[i]现在面临得问题就是单个修还是连着修,那么我们不妨把这些结果一一算出来比较大小。
我们用一个j去枚举连续修j个栅栏时候得情况那么我们如果连续修得话,结果自然就是f[i-j]+sqrt(j),所以我们只需要比较这些f[i-j]+sqrt(j)找到最小得一个,他就是f[i]得值。
从而得到方程:f[i]=min(f[i],f[i-j]+sqrt(j));
上代码!
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n; 4 double f[10005]; 5 int wall[100005]; 6 const int oo=0x7f7f7f; 7 int main(){ 8 scanf("%d",&n); 9 for(int i=1;i<=n;i++)scanf("%d",&wall[i]); 10 for(int i=1;i<=n;i++){ 11 if(!wall[i]){ //判断如果到达一个需要修得点得时候就判断怎么修这个点 12 f[i]=oo; 13 for(int j=1;j<=i;j++){ //枚举修j个栅栏得情况 14 f[i]=min(f[i],f[i-j]+sqrt(j)); //状态转移方程 15 } 16 } 17 else f[i]=f[i-1]; //如果不需要修那么就继承上一代得意志(此时得到的最优值就是上一个的值 18 } 19 printf("%.3f",f[n]); //别忘了3位小数 20 return 0; 21 }
-----------------------------------------------
个性签名:天生我材必有用,千金散尽还复来!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!