2018.02.04(补作业系列)

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\n",f[1][n]);
23     return 0;
24 }
View Code

状态转移方程&解析: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("\n%ld\n",maxx);
44     return 0;
45 }
View Code

状态转移方程&解析:

很明显,题目所规定的所有路径都是单向的,所以满足无后效性原则和最优化原理。设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\n",maxx);
36     return 0;
37 }
View Code

状态转移方程&解析:

我们将每对友好城市看成一条线段,则这道题的描述化为:有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\n",sum[n][n][n][n]);
27     return 0;
28 }
View Code

状态转移方程&解析:

一个四重循环枚举两条路分别走到的位置。由于每个点均从上或左继承而来,故内部有四个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\n",f[n][k1]);
26     return 0;
27 }
View Code

状态转移方程&解析:

我们把它按插入的乘号来划分阶段,若插入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 }
View Code

状态:AC

转载于:https://www.cnblogs.com/yzyl-Leo-wey/p/ZuoYeZhongYuBuWanLa.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值