2018.02.04
补作业系列
1.合并石子
思路:如解析所示,对于动态规划的题目,只要有了思路,就容易了。不过一本通上的此题比超链接的此题多了一个条件:每次只能合并相邻的两堆石子。所以写状态转移方程就更容易了。
核心代码:
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int _Min(int x,int y){return x<y?x:y;} 5 int f[101][101]; 6 int s[101]; 7 int n,i,j,k,x; 8 int main(){ 9 scanf("%d",&n); 10 for(i=1;i<=n;i++){ 11 scanf("%d",&x); 12 s[i]=s[i-1]+x; 13 } 14 memset(f,20000,sizeof(f)); 15 for(i=1;i<=n;i++)f[i][i]=0; 16 for(i=n-1;i>=1;i--){ 17 for(j=i+1;j<=n;j++){ 18 for(k=i;k<=j-1;k++) 19 f[i][j]=_Min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]); 20 } 21 } 22 printf("%d ",f[1][n]); 23 return 0; 24 }
状态转移方程&解析:s[i]表示前i堆石子的数量总和,f[i][j]表示把第i堆石子到第j堆石子合并成一堆的最优值。
for ( i = n-1 ; i >= 1 ; i-- ){ for ( j = i+1 ; j <= n ; j++ ){ for ( k = i ; k <= j-1 ; k++ ){ f[i][j] = min ( f[i][j] , f[i][k] + f[k+1][j] + s[j] -s[i-1] ); } } } 输出f[1][n]
状态:AC
2.挖地雷
思路:如解析所示,最值得注意的是该题对于状态和阶段的定义,只要将此概念明确,写代码就很容易了。
核心代码:
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int main(){ 5 long f[201]={0},w[201],c[201]={0}; 6 int a[201][201]={0}; 7 long i,j; 8 long n; 9 long x,y; 10 long l,k,maxx; 11 memset(f,0,sizeof(f)); 12 memset(c,0,sizeof(c)); 13 memset(a,0,sizeof(a)); 14 scanf("%d",&n); 15 for(i=1;i<=n;i++) 16 scanf("%ld",&w[i]); 17 for(i=1;i<n;i++){ 18 for(j=i+1;j<=n;j++){ 19 scanf("%d",&x); 20 if(x==1)a[i][j]=1; 21 } 22 } 23 f[n]=w[n]; 24 for(i=n-1;i>=1;i--){ 25 l=0;k=0; 26 for(j=i+1;j<=n;j++) 27 if((a[i][j])&&(f[j]>l)){ 28 l=f[j];k=j; 29 } 30 f[i]=l+w[i]; 31 c[i]=k; 32 } 33 k=1; 34 for(j=2;j<=n;j++) 35 if(f[j]>f[k])k=j; 36 maxx=f[k]; 37 printf("%ld",k); 38 k=c[k]; 39 while(k!=0){ 40 printf(" %ld",k); 41 k=c[k]; 42 } 43 printf(" %ld ",maxx); 44 return 0; 45 }
状态转移方程&解析:
很明显,题目所规定的所有路径都是单向的,所以满足无后效性原则和最优化原理。设w[i]为第i个地窖所藏有的地雷数,a[i][j]表示第i个地窖与第j个地窖之间是否有通路,f[i]为从第i个地窖开始最多可以挖出的地雷数,则有如下递归式:
f[i]=max{ w[i]+f[j] } ( i<j<=n && a[i][j]=true ) 边界: f[n]=w[n]
于是就可以通过递推的方法,从后面的f(n)往前逐个找出所有的f[i],再从中找出一个最大的即为问题二的解。对于具体走的路径(问题一),可以通过一个向后的链接来实现。
状态:AC
3.友好城市
思路:对于南岸和北岸的两组坐标,把其中一组进行排序后,会发现其实就是求另外一组的最长不下降序列。但是排序的话,数组行不通,要STL的结构体排序,还是不知道排。抄了一个题解,依葫芦画瓢打出来了。据说还有一个O(n logn)的做法,没时间研究了,下次再说。
核心代码:
1 #include <cstdio> 2 #include <cmath> 3 #include <cstring> 4 #include <iostream> 5 #include <algorithm> 6 using namespace std; 7 struct data{ 8 int south; 9 int north; 10 }; 11 int sum[200001]; 12 int n,maxx=1; 13 bool cmp(data a,data b){return a.south<b.south;} 14 int _Max(int x,int y){return x>y?x:y;} 15 int main(){ 16 struct data city[200001]; 17 scanf("%d",&n); 18 int i,j; 19 20 for(i=1;i<=n;i++) 21 scanf("%d%d",&city[i].south,&city[i].north); 22 23 for(i=0;i<=n;i++) 24 sum[i]=1; 25 26 sort(city+1,city+n+1,cmp); 27 for(i=1;i<=n;i++){ 28 for(j=i+1;j<=n;j++){ 29 if(city[i].north<=city[j].north){ 30 sum[j]=_Max(sum[j],sum[i]+1); 31 } 32 } 33 maxx=_Max(maxx,sum[i]); 34 } 35 printf("%d ",maxx); 36 return 0; 37 }
状态转移方程&解析:
我们将每对友好城市看成一条线段,则这道题的描述化为:有N条线段,问最少去掉多少条线,可以使剩下的线段互不交叉?以北岸为线的起点而南岸为线的终点;先将所有的线按照起点坐标值从小到大排序,可以发现:只要线J的起点小于线I的起点,同时它的终点也小于线I的终点,则这两条线不相交。因此,求所有线中最多能有多少条线不相交,实际上就是从终点坐标值序列中求一个最长不下降子序列。这就把题目转换成非常经典的动态规划题目了。状态转移方程如下:
L[i]=max{ L[j] } +1 ; 1<=j<i 且 Sj < Si ,Si 为航线的终点坐标值
状态:AC
4.方格取数
思路:用四重循环,模拟两条路同时走的所有四种情况,(其实就是数字三角形的升升升级版)用四维数组保存最优情况,最后输出sum[n][n][n][n]。
核心代码:
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int _Max(int x,int y){return x>y?x:y;} 5 int a[51][51],sum[51][51][51][51]; 6 int n,i,j; 7 int h,k,x,y,z; 8 int main(){ 9 scanf("%d%d%d%d",&n,&x,&y,&z); 10 while(x&&y&&z){ 11 a[x][y]=z; 12 scanf("%d%d%d",&x,&y,&z); 13 } 14 for(i=1;i<=n;i++){ 15 for(j=1;j<=n;j++){ 16 for(h=1;h<=n;h++){ 17 for(k=1;k<=n;k++){ 18 int tmp1=_Max(sum[i-1][j][h-1][k],sum[i][j-1][h][k-1]); 19 int tmp2=_Max(sum[i][j-1][h-1][k],sum[i-1][j][h][k-1]); 20 sum[i][j][h][k]=_Max(tmp1,tmp2)+a[i][j]; 21 if(i!=h&&j!=k)sum[i][j][h][k]+=a[h][k]; 22 } 23 } 24 } 25 } 26 printf("%d ",sum[n][n][n][n]); 27 return 0; 28 }
状态转移方程&解析:
一个四重循环枚举两条路分别走到的位置。由于每个点均从上或左继承而来,故内部有四个if,分别表示两个点从上上,上左,左上,左左继承来时,加上当前两个点所取得的最大值。a[i][j]表示 ( i , j ) 格上的值,sum[i][j][h][k]表示第一条路走到 ( i , j ),第二条路走到 ( h , k ) 时的最优解。当( i , j ) != ( h , k )时:sum[i][j][h][k]=max( sum[i-1][j][h-1][k] , sum[i][j-1][h][k-1] , sum[i][j-1][h-1][k] , sum[i-1][j][h][k-1] ) + a[i][j] + a[h][k]。当( i , j ) == ( h , k )时:sum[i][j][h][k]=max( sum[i-1][j][h-1][k] , sum[i][j-1][h][k-1] , sum[i][j-1][h-1][k] , sum[i-1][j][h][k-1] ) + a[i][j] 。
状态:AC
5.乘积最大
思路:对于状态的定义是这样的:f[i][k]表示在前i位数中插入k个乘号所得的最大值,a[j][i]表示从第j位到第i位所组成的自然数。
核心代码:
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 long long a[41][41],f[41][41]; 5 long long s; 6 int n,k,k1; 7 int i,j; 8 int _Max(int x,int y){return x>y?x:y;} 9 int main(){ 10 scanf("%d%d",&n,&k1); 11 scanf("%lld",&s); 12 for(i=n;i>=1;i--){ 13 a[i][i]=s%10; 14 s/=10; 15 } 16 for(i=2;i<=n;i++) 17 for(j=i-1;j>=1;j--) 18 a[j][i]=a[j][i-1]*10+a[i][i]; 19 for(i=1;i<=n;i++) 20 f[i][0]=a[1][i]; 21 for(k=1;k<=k1;k++) 22 for(i=k+1;i<=n;i++) 23 for(j=k;j<i;j++) 24 f[i][k]=_Max(f[i][k],f[j][k-1]*a[j+1][i]); 25 printf("%lld ",f[n][k1]); 26 return 0; 27 }
状态转移方程&解析:
我们把它按插入的乘号来划分阶段,若插入k个乘号,可把问题看作是k个阶段的决策问题。设f[i][k]表示在前i位数中插入k个乘号所得的最大值,a[j][i]表示从第j位到第i位所组成的自然数。用f[i][k]储存阶段k的每一个状态,可以获得状态转移方程:
f[i][k] = max { f[j][k-1] * a[j+1][i] } ( k <= j < i ) 边界值: f[j][0]=a[1][j] ( 1 <= j <= n )
根据状态转移方程,我们就可以很容易写出动态规划程序:
for (j = 1 ; k a<= k1 ; k++) for (i= k + 1 ; i <= n ; i++) for (j = k ; j < i ; j++) f[i][k] = max( f[i][k] , f[j][k-1] * a[j+1][i] );
状态:UNAC(要用高精度,long long只有20分)
6.公共子序列
思路:...以后再补吧
核心代码:
1 #include <cstdio> 2 #include<cmath> 3 #include<cstring> 4 #include<iostream> 5 using namespace std; 6 int main(){ 7 char a[210],b[210]; 8 int f[201][201]; 9 int l1,l2,i,j; 10 while(cin>>a+1>>b+1){ 11 memset(f,0,sizeof(f)); 12 l1=strlen(a); 13 l2=strlen(b); 14 for(i=1;i<l1;i++) 15 for(j=1;j<l2;j++) 16 if(a[i]==b[j]&&f[i-1][j-1]+1>f[i][j]) 17 f[i][j]=f[i-1][j-1]+1; 18 else 19 f[i][j]=max(f[i-1][j],f[i][j-1]); 20 cout<<f[l1-1][l2-1]<<endl; 21 } 22 return 0; 23 }
状态:AC