动态规划题集

hdu 2084 数塔

由上往下推,由于结果状态多,不好处理

由底往上推,都归于一个起点,只需要算出由底往上得到的最大价值即可

方程:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];

 

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. int a[105][105],dp[105][105];  
  7.   
  8. int main()  
  9. {  
  10.     int t,n,i,j;  
  11.     scanf("%d",&t);  
  12.     while(t--)  
  13.     {  
  14.         scanf("%d",&n);  
  15.         memset(dp,0,sizeof(dp));  
  16.         for(i = 1;i<=n;i++)  
  17.         {  
  18.             for(j = 1;j<=i;j++)  
  19.             scanf("%d",&a[i][j]);  
  20.         }  
  21.         for(i = n;i>=1;i--)  
  22.         {  
  23.             for(j = 1;j<=i;j++)  
  24.             {  
  25.                 dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];  
  26.             }  
  27.         }  
  28.         printf("%d\n",dp[1][1]);  
  29.     }  
  30.   
  31.     return 0;  
  32. }  

hdu 2018 母牛的故事

以n=6为例,fn=9头牛可以分解为6+3,其中6是上一年(第5年)的牛,3是新生的牛(因为第3年有3头牛,这3头在第6年各生一头牛)。
  我们可以得出这样一个公式:fn=fn-1+fn-3
  再理解一下,fn-1是前一年的牛,第n年仍然在,fn-3是前三年那一年的牛,但换句话说也就是第n年具有生育能力的牛,也就是第n年能生下的小牛数。
  编程序,求解这个公式就行了。
  当然,第1-3年的数目,需要直接给出。
  很像斐波那契数列,有不一样之处,道理、方法一样。其实,在编程之前,讲究先用这样的方式建模。

  下面给出参考程序:

[cpp]  view plain  copy
  1. //解法1:迭代解法  
  2. #include <iostream>  
  3. using namespace std;  
  4. int main()  
  5. {  
  6.     int n,i;  
  7.     int f1, f2, f3, fn;  
  8.     while(cin>>n&&n!=0)  
  9.     {  
  10.         //下面求第n年有几头牛  
  11.         f1=1;  
  12.         f2=2;  
  13.         f3=3;  
  14.         if(n==1)  
  15.             cout<<f1<<endl;  
  16.         else if(n==2)  
  17.             cout<<f2<<endl;  
  18.         else if(n==3)  
  19.             cout<<f3<<endl;  
  20.         else  
  21.         {  
  22.             for(i=4; i<=n; i++)  
  23.             {  
  24.                 fn=f3+f1;  
  25.                 f1=f2;  //f1代表前3年  
  26.                 f2=f3;  //f2代表前2年  
  27.                 f3=fn;  //f3代表前1年  
  28.             }  
  29.             cout<<fn<<endl;  
  30.         }  
  31.     }  
  32.     return 0;  
  33. }  


[cpp]  view plain  copy
  1. //解法2:定义递归函数(效率低,不建议用)  
  2. #include <iostream>  
  3. using namespace std;  
  4. int f(int n);  
  5. int main()  
  6. {  
  7.     int n;  
  8.     while(cin>>n&&n!=0)  
  9.     {  
  10.         cout<<f(n)<<endl;  
  11.     }  
  12.     return 0;  
  13. }  
  14.   
  15.   
  16. int f(int n)  
  17. {  
  18.     if(n<4)  
  19.         return n; //第1,2,3年,各为1,2,3头  
  20.     else  
  21.         return f(n-1)+f(n-3);  //第n年为前一年的和前3年的相加  
  22. }  

[cpp]  view plain  copy
  1. //解法3:用数组  
  2. #include <iostream>  
  3. using namespace std;  
  4. int main()  
  5. {  
  6.     int n,i;  
  7.     int f[56]={0,1,2,3};  
  8.     for(i=4;i<=55;i++)  
  9.         f[i]=f[i-1]+f[i-3];  
  10.     while(cin>>n&&n!=0)  
  11.     {  
  12.         cout<<f[n]<<endl;  
  13.     }  
  14.     return 0;  
  15. }  
hdu 2044 一只小蜜蜂...  

  1. #include <stdio.h>  
  2. //#include <cmath>  
  3. //#include <cstring>  
  4. int main()  
  5. {  
  6.     int n, a, b;  
  7.     __int64 c[55];  
  8.     c[2] = 1;  
  9.     c[3] = 2;  
  10.     for(int i=4; i<=50; i++)  
  11.     {  
  12.         c[i] = c[i-1] + c[i-2];  
  13.     }  
  14.     scanf("%d", &n);  
  15.     while(n--)  
  16.     {  
  17.         scanf("%d%d", &a, &b);  
  18.         printf("%I64d\n", c[b-a+1]);  
  19.     }  
  20.     return 0;  
  21. }  

hdu 2041 超级楼梯 Fibonacci

  1. #include <cstdio>  
  2. #include <cmath>  
  3. int step[50];  
  4. int main()  
  5. {  
  6.     int n,m;  
  7.     scanf("%d",&n);  
  8.     while(n--)  
  9.     {  
  10.         scanf("%d",&m);  
  11.         step[1] = 1;  
  12.         step[2] = 2;  
  13.         for(int i=3; i<m; i++)  
  14.         {  
  15.             step[i] = step[i-1] + step[i-2];  
  16.         }  
  17.         printf("%d\n",step[m-1]);  
  18.     }  
  19.     return 0;  
  20. }  

hdu 2050 折线分割平面 找递推公式

1递推递推,先分析下直线分割平面的情况,增加第n条直线的时候,跟之前的直线最多有n-1个交点,此时分出的部分多出了(n-1)+1;

2折线也是同理,f(1)=2,f(2)=7,先画好前面n-1条折线,当增加第n条拆线时,此时与图形新的交点最多有2*2(n-1)个,所以分出的部分多出了2*2(n-1)+1   所以推出f(n)=f(n-1)+4*(n-1)+1,n>=3

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int n,i,k,j;  
  5.     __int64 a[10010];  
  6.     scanf("%d",&n);  
  7.     for(i=1;i<=n;i++)  
  8.     {  
  9.         scanf("%d",&k);  
  10.         a[1]=2;  
  11.         for(j=2;j<=k;j++)  
  12.             a[j]=a[j-1]+4*(j-1)+1;  
  13.         printf("%I64d\n",a[k]);  
  14.     }  
  15.     return 0;  
  16. }  

Codeforces 429B B. Working out

给n*m的矩阵,每个格子有个数,A从(1,1)出发只能向下或右走,终点为(n,m),B从(n,1)出发只能向上或右走,终点为(1,m)。两个人的速度不一样,走到的格子可以获的该格子的数,两人相遇的格子上的数两个人都不能拿。求A和B能拿到的数的总和的最大值。

n,m<=1000

解题思路:

dp.

先预处理出每个格子到四个角落格子的路径最大数值,然后枚举两个人相遇的交点格子,枚举A、B的进来和出去方式,求最大值即可。

注意边界情况。

代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //#include<CSpreadSheet.h>  
  2.   
  3. #include<iostream>  
  4. #include<cmath>  
  5. #include<cstdio>  
  6. #include<sstream>  
  7. #include<cstdlib>  
  8. #include<string>  
  9. #include<string.h>  
  10. #include<cstring>  
  11. #include<algorithm>  
  12. #include<vector>  
  13. #include<map>  
  14. #include<set>  
  15. #include<stack>  
  16. #include<list>  
  17. #include<queue>  
  18. #include<ctime>  
  19. #include<bitset>  
  20. #include<cmath>  
  21. #define eps 1e-6  
  22. #define INF 0x3f3f3f3f  
  23. #define PI acos(-1.0)  
  24. #define ll __int64  
  25. #define LL long long  
  26. #define lson l,m,(rt<<1)  
  27. #define rson m+1,r,(rt<<1)|1  
  28. #define M 1000000007  
  29. //#pragma comment(linker, "/STACK:1024000000,1024000000")  
  30. using namespace std;  
  31.   
  32. #define Maxn 1100  
  33. ll dp[5][Maxn][Maxn];  
  34. int n,m;  
  35.   
  36. ll save[Maxn][Maxn];  
  37.   
  38.   
  39. int main()  
  40. {  
  41.    //freopen("in.txt","r",stdin);  
  42.    //freopen("out.txt","w",stdout);  
  43.    while(~scanf("%d%d",&n,&m))  
  44.    {  
  45.        memset(save,0,sizeof(save));  
  46.   
  47.        for(int i=1;i<=n;i++)  
  48.             for(int j=1;j<=m;j++)  
  49.                 scanf("%d",&save[i][j]);  
  50.   
  51.        memset(dp,0,sizeof(dp));  
  52.        for(int i=1;i<=n;i++)  
  53.        {  
  54.            for(int j=1;j<=m;j++)  
  55.                dp[1][i][j]=max(dp[1][i-1][j],dp[1][i][j-1])+save[i][j];  
  56.                //printf("i:%d j:%d %I64d\n",i,j,dp[1][i][j]);  
  57.            for(int j=m;j>=1;j--)  
  58.                 dp[3][i][j]=max(dp[3][i-1][j],dp[3][i][j+1])+save[i][j];  
  59.        }  
  60.   
  61.        for(int i=n;i>=1;i--)  
  62.        {  
  63.            for(int j=1;j<=m;j++)  
  64.                 dp[2][i][j]=max(dp[2][i+1][j],dp[2][i][j-1])+save[i][j];  
  65.            for(int j=m;j>=1;j--)  
  66.                 dp[4][i][j]=max(dp[4][i+1][j],dp[4][i][j+1])+save[i][j];  
  67.        }  
  68.        ll ans=0;  
  69.        for(int i=0;i<=n+1;i++)  //把四个边界都置为无效情况  
  70.             for(int k=1;k<=4;k++)  
  71.                 dp[k][i][0]=dp[k][i][m+1]=-INF;  
  72.        for(int j=0;j<=m+1;j++)  
  73.             for(int k=1;k<=4;k++)  
  74.                 dp[k][0][j]=dp[k][n+1][j]=-INF;  
  75.   
  76.        for(int i=1;i<=n;i++)  
  77.        {  
  78.            for(int j=1;j<=m;j++)  
  79.            {     //只用考虑两种情况 两个人只能交叉一个格子,多了的话不合算  
  80.                ll temp=dp[1][i][j-1]+dp[4][i][j+1]+dp[2][i+1][j]+dp[3][i-1][j];  
  81.                ans=max(ans,temp);  
  82.                temp=dp[1][i-1][j]+dp[4][i+1][j]+dp[2][i][j-1]+dp[3][i][j+1];  
  83.                ans=max(ans,temp);  
  84.                //printf("i:%d j:%d %I64d\n",i,j,ans);  
  85.            }  
  86.        }  
  87.        printf("%I64d\n",ans);  
  88.    }  
  89.    return 0;  
  90. }  

背包问题//背包九讲http://love-oriented.com/pack/Index.html

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<iostream>  
  2. using namespace std;  
  3. #define  V 1500  
  4. unsigned int f[V];//全局变量,自动初始化为0  
  5. unsigned int weight[10];  
  6. unsigned int value[10];  
  7. #define  max(x,y)   (x)>(y)?(x):(y)  
  8. int main()  
  9. {  
  10.       
  11.     int N,M;  
  12.     cin>>N;//物品个数  
  13.     cin>>M;//背包容量  
  14.     for (int i=1;i<=N; i++)  
  15.     {  
  16.         cin>>weight[i]>>value[i];  
  17.     }  
  18.     for (int i=1; i<=N; i++)  
  19.         for (int j=M; j>=1; j--)  
  20.         {  
  21.             if (weight[i]<=j)  
  22.             {  
  23.                 f[j]=max(f[j],f[j-weight[i]]+value[i]);  
  24.             }             
  25.         }  
  26.       
  27.     cout<<f[M]<<endl;//输出最优解  
  28.   
  29. }  

在看完01背包问题,再来看完全背包问题: 一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],每个物品都有无限多件,现在往背包里面装东西,怎么装能使背包的内物品价值最大?

对比一下,看到的区别是,完全背包问题中,物品有无限多件。往背包里面添加物品时,只要当前背包没装满,可以一直添加。那么状态转移方程为:

f[i+1][j]=max(f[i][j-k*weight[i+1]+k*value[i+1]),其中0<=k<=V/weight[i+1]

使用内存为一维数组,伪代码

for i=1……N

for j=1……M

f[j]=max(f[j],f[j-weight[i]+value[i])

和01背包问题唯一不同的是j是从1到M。01背包问题是在前一个子问题(i-1 物品)的基础上来解决当前问题(i 物品),向i-1种物品时的背包添加第i种物品;而完全背包问题是在解决当前问题(i种物品),向i种物品时的背包添加第i种物品。

代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<iostream>  
  2. using namespace std;  
  3. #define  V 1500  
  4. unsigned int f[V];//全局变量,自动初始化为0  
  5. unsigned int weight[10];  
  6. unsigned int value[10];  
  7. #define  max(x,y)   (x)>(y)?(x):(y)  
  8. int main()  
  9. {  
  10.       
  11.     int N,M;  
  12.     cin>>N;//物品个数  
  13.     cin>>M;//背包容量  
  14.     for (int i=1;i<=N; i++)  
  15.     {  
  16.         cin>>weight[i]>>value[i];  
  17.     }  
  18.     for (int i=1; i<=N; i++)  
  19.         for (int j=1; j<=M; j++)  
  20.         {  
  21.             if (weight[i]<=j)  
  22.             {  
  23.                 f[j]=max(f[j],f[j-weight[i]]+value[i]);  
  24.             }             
  25.         }  
  26.       
  27.     cout<<f[M]<<endl;//输出最优解  
  28.   
  29. }  


最小纸币数

给定一定钱,币面有1,21,25,2,5几种,求组成该钱数的最少张数并输出方案。

void solve(vector<int>&money,int sum){
    vector<int> dp(sum+1,INT_MAX);//i元需要最少纸币张数。
    vector<int> res(sum+1,0);//存方案
    dp[0]=0;
    for(int i=1;i<=sum;i++){
        for(int k=0;k<money.size();k++){
            if(money[k]<=i){
                if(dp[i]>dp[i-money[k]]+1){
                    dp[i]=dp[i-money[k]]+1;
                    res[i]=money[k];
                }
            }
        }
    }
    int s=sum;
    while(res[s]){
        cout<<res[s]<<" ";
        s-=res[s];
    }
}
int main(){
    vector<int> money={1,21,25,2,5};
    int sum;
    cin>>sum;
    solve(money,sum);
}




多重背包问题

题目:有N种物品和一个容量为V的背包。第i种物品最多有num[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装

背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 

 

分析:状态转移为:

 

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191

 

[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. const int N = 1005;  
  7.   
  8. int dp[N];  
  9. int c[N],w[N],num[N];  
  10. int n,m;  
  11.   
  12. void ZeroOne_Pack(int cost,int weight,int n)  
  13. {  
  14.     for(int i=n; i>=cost; i--)  
  15.         dp[i] = max(dp[i],dp[i-cost] + weight);  
  16. }  
  17.   
  18. void Complete_Pack(int cost,int weight,int n)  
  19. {  
  20.     for(int i=cost; i<=n; i++)  
  21.         dp[i] = max(dp[i],dp[i-cost] + weight);  
  22. }  
  23.   
  24. int Multi_Pack(int c[],int w[],int num[],int n,int m)  
  25. {  
  26.     memset(dp,0,sizeof(dp));  
  27.     for(int i=1; i<=n; i++)  
  28.     {  
  29.         if(num[i]*c[i] > m)  
  30.             Complete_Pack(c[i],w[i],m);  
  31.         else  
  32.         {  
  33.             int k = 1;  
  34.             while(k < num[i])  
  35.             {  
  36.                 ZeroOne_Pack(k*c[i],k*w[i],m);  
  37.                 num[i] -= k;  
  38.                 k <<= 1;  
  39.             }  
  40.             ZeroOne_Pack(num[i]*c[i],num[i]*w[i],m);  
  41.         }  
  42.     }  
  43.     return dp[m];  
  44. }  
  45.   
  46. int main()  
  47. {  
  48.     int t;  
  49.     cin>>t;  
  50.     while(t--)  
  51.     {  
  52.         cin>>m>>n;  
  53.         for(int i=1; i<=n; i++)  
  54.             cin>>c[i]>>w[i]>>num[i];  
  55.         cout<<Multi_Pack(c,w,num,n,m)<<endl;  
  56.     }  
  57.     return 0;  
  58. }  

一个强盗要去抢劫银行,对于每个银行来说,都有一个不被抓的概率p,和能抢劫到的钱数money,每个银行最多只可以被抢劫一次。问在不被抓的总概率P下,怎样得到最大价值的钱数。

分析:我第一眼看的时候觉得见到01,然后弄一个dp[100],把概率*100。。。。。。如果你和我一样这么想。。那么恭喜你上当了!需要把money当做下标。。。保存的是概率。。。状态转移方程:dp[j] = max(dp[j], (1.0-gai[i]) * dp[j-money[i]]);

[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. using namespace std;  
  5.   
  6. #define max(a,b)    ((a)>(b)?(a):(b))  
  7. const int maxn = 105;  
  8. double dp[10005];  
  9. int main()  
  10. {  
  11.     int t;  
  12.     scanf("%d", &t);  
  13.     while (t--)  
  14.     {  
  15.         double p, gai[maxn];  
  16.         int n, money[maxn];  
  17.         scanf("%lf %d", &p, &n);  
  18.         int i, j;  
  19.         int sum = 0;  
  20.         for (i=0; i<n; i++)  
  21.         {  
  22.             scanf("%d %lf", &money[i], &gai[i]);  
  23.             sum += money[i];  
  24.         }  
  25.         memset(dp, 0, (sum+1)*sizeof(dp[0]));  
  26.         dp[0] = 1;  
  27.   
  28.         if (p <= 1-1e-8)  
  29.         {  
  30.             for (i=0; i<n; i++)  
  31.                 for (j=sum; j>=money[i]; j--)  
  32.                 {  
  33.                     double tmp = (1.0-gai[i]) * dp[j-money[i]];  
  34.                     dp[j] = max(dp[j], tmp);  
  35.                 }  
  36.   
  37.             for (i=sum; 1 - dp[i] >= p; i--);  
  38.             printf("%d\n", i);  
  39.         }  
  40.         else  
  41.         {  
  42.             printf("%d\n", sum);  
  43.         }  
  44.     }  
  45.     return 0;  
  46. }  



最大连续子数组和

int solve(vector<int>& v){
    int n=v.size();
    vector<int> dp(n,0);//dp[i]:以i为结尾的子数组元素和 
    dp[0]=v[0];
    int res=dp[0];
    for(int i=1;i<n;i++){
        dp[i]=max(dp[i-1]+v[i],v[i]);
        res=max(res,dp[i]);
    }
    return res;
}
//空间优化 
int solve2(vector<int>& v){
    int n=v.size();

    int thissum=v[0];
    int res=thissum;
    for(int i=1;i<n;i++){
        thissum=max(thissum+v[i],v[i]);
        res=max(res,thissum);
    }
    return res;
}

最大递增子序列和

#include<iostream>
#include<vector>
using namespace std;
#define max(a,b) ((a)>(b)?(a):(b))
int solve(vector<int>&v){
    int n=v.size();
    vector<int> dp(n,0);
    dp[0]=v[0];
    int res=-1000000;
    for(int i=1;i<n;i++){
        dp[i]=v[i];
        for(int j=0;j<i;j++){
           if(v[i]>v[j]){
               dp[i]=max(dp[i],dp[j]+v[i]);
           }
         }
         res=max(res,dp[i]);
    }
    return res;
}
int main(){
    int n;
     while(cin>>n){
     	if(n==0) continue;
        vector<int> v(n);
        for(int i=0;i<n;i++) cin>>v[i];
        cout<<solve(v)<<endl;
     }
     return 0;
  }
    




最长递增子序列

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        if(n==0) return 0;
        if(n==1) return 1;
        int res=0;
        vector<int>dp(n,1);
        for(int i=1;i<n;i++){
            for(int j=i-1;j>=0;j--){
                if(nums[i]>nums[j]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        return *max_element(dp.begin(),dp.end());
    }
};


最长公共子序列

用二维数组c[i][j]记录串 x1x2xi x1x2⋯xi y1y2yj y1y2⋯yj的LCS长度,则可得到状态转移方程

c[i,j]=0c[i1,j1]+1max(c[i,j1],c[i1,j])i=0 or j=0i,j>0 and  xi=yji,j>0 and xiyj c[i,j]={0i=0 or j=0c[i−1,j−1]+1i,j>0 and  xi=yjmax(c[i,j−1],c[i−1,j])i,j>0 and xi≠yj

代码实现

public static int lcs(String str1, String str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    int c[][] = new int[len1+1][len2+1];
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
                c[i][j] = c[i-1][j-1] + 1;
            } else {
                c[i][j] = max(c[i - 1][j], c[i][j - 1]);
            }
        }
    }
    return c[len1][len2];
}

DP求解最长公共子串

前面提到了子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组 c[i,j] c[i,j]用来记录具有这样特点的子串——结尾为母串 x1x2xi x1x2⋯xi y1y2yj y1y2⋯yj的结尾——的长度。

得到转移方程:

c[i,j]=0c[i1,j1]+10i=0 or j=0xi=yjxiyj c[i,j]={0i=0 or j=0c[i−1,j−1]+1xi=yj0xi≠yj

最长公共子串的长度为  max(c[i,j]), i{1,,m},j{1,,n} max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n}

代码实现

public static int lcs(String str1, String str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    int result = 0;     //记录最长公共子串长度
    int c[][] = new int[len1+1][len2+1];
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
                c[i][j] = c[i-1][j-1] + 1;
                result = max(c[i][j], result);
            } else {
                c[i][j] = 0;
            }
        }
    }
    return result;
}

给定整数m,n和数组x[n],找出某个i,使得x[i]+x[i+1]+x[i+2]+x[i+3]+x[i+4]…x[i+m]最接近于零。

(0<=i<n-m)

一.暴力解法

    遍历各个i值,计算子序列的和,然后求出最接近0的

int find(int a[],int n,int m)     //寻找m+1个数字,使得他们的和最小
{
	int i=0;
	int thissum=0;
	int j=0;
	int ans=INT_MAX;
	for(i=0;i<n-m;i++)         //时间复杂度为O(m*n)
	{
		thissum=0;
	for(j=i;j<=i+m;j++)
		thissum+=a[j];
	if(abs((int)(thissum))<abs((int)(ans)))
		ans=abs((int)thissum);
	}
	return ans;
}

显然,时间复杂度为O(m*n),空间复杂度为O(1)

二.动态规划:

    建立数组dp[],dp[i]存放的是x[i]+x[i+1]+…x[i+m]的值,则递推关系式为:

dp[i+1]=dp[i]-a[i]+a[i+1+m],然后遍历dp[]数组,求出最近于零的。

int find2(int a[],int n,int m)
{
int *dp=(int *)malloc(n*sizeof(int));
int i=0;
int tmp=0;
for(i=0;i<=m;i++)
{
	tmp+=a[i];
}
dp[0]=tmp;
for(i=1;i<n-m;i++)
{
dp[i]=dp[i-1]+a[i+m]-a[i-1];
}
int ans=INT_MAX;
for(i=0;i<n;i++)
{
	if(abs((int)dp[i])<ans)
		ans=abs((int)dp[i]);
}
return ans;
}
    时间复杂度为O(n),空间复杂度为O(n)

    还可以对上面的代码稍作改进,上面的dp[]数组,只要用一个变量即可。

int find3(int a[],int n,int m)   
{
int tmp=0;
int ans=INT_MAX;
int i=0;
for(i=0;i<=m;i++)
	tmp+=a[i];
if(abs((int)tmp)<ans)
	ans=abs((int)tmp);
for(i=1;i<n-m;i++)
{
	tmp=tmp-a[i-1]+a[i+m];
	if(abs((int)tmp)<ans)
		ans=abs((int)tmp);
}
return ans;
}

    时间复杂度为O(n),空间复杂度为O(1)

完整代码为:

# include <iostream>
# include <cstdlib>
# include <cmath>
# include <ctype.h>
using namespace std;

int find(int a[],int n,int m)     //寻找m+1个数字,使得他们的和最小
{
	int i=0;
	int thissum=0;
	int j=0;
	int ans=INT_MAX;
	for(i=0;i<n-m;i++)         //时间复杂度为O(m*n)
	{
		thissum=0;
	for(j=i;j<=i+m;j++)
		thissum+=a[j];
	if(abs((int)(thissum))<abs((int)(ans)))
		ans=abs((int)thissum);
	}
	return ans;
}

int find2(int a[],int n,int m)
{
int *dp=(int *)malloc(n*sizeof(int));
int i=0;
int tmp=0;
for(i=0;i<=m;i++)
{
	tmp+=a[i];
}
dp[0]=tmp;
for(i=1;i<n-m;i++)
{
dp[i]=dp[i-1]+a[i+m]-a[i-1];
}
int ans=INT_MAX;
for(i=0;i<n;i++)
{
	if(abs((int)dp[i])<ans)
		ans=abs((int)dp[i]);
}
return ans;
}

int find3(int a[],int n,int m)   //  
{
int tmp=0;
int ans=INT_MAX;
int i=0;
for(i=0;i<=m;i++)
	tmp+=a[i];
if(abs((int)tmp)<ans)
	ans=abs((int)tmp);
for(i=1;i<n-m;i++)
{
	tmp=tmp-a[i-1]+a[i+m];
	if(abs((int)tmp)<ans)
		ans=abs((int)tmp);
}
return ans;
}

int main()
{
	int a[10]={1,-2,3,-4,-5,-6,7,8,9,0};
	cout<<find(a,10,3)<<endl;
	cout<<find2(a,10,3)<<endl;
	cout<<find3(a,10,3)<<endl;
system("pause");
return 0;
}

一个数组中只有0和1,求0和1个数相等的最大连续子序列?【百度面试题】


这题有O(N)空间和时间的解法

首先把数组中的 0 全部改成 -1

此时题目等价于询问一段最长的区间,使得区间内的数的和是0
这个时候可以对新的 1/-1 数组求前缀和数组sum
题目又转化为求两个下标 i,j使得 sum[i] = sum[j],且|i-j| 最大

这个可以用一个长度为 2N+1 的数组来记录前缀和的下标来进行快速查询

细节见代码
#include <iostream>
#include <vector>
using namespace std;


int main() {
	vector<int> arr = { 1, 1, 1, 0, 1,0,1,1 };
	int counts = arr.size();
	vector<int> sum;
	sum.reserve(counts + 1);
	sum.push_back(0);
	for (const auto& x : arr) {
		sum.push_back(sum.back() + x * 2 - 1);
	}
	vector<int> temp(counts * 2 + 1, -1);//哈希表
	int maxlen = -1;
	int startindex = -1, endindex = -1;
	for (int i = 0; i < sum.size(); i++) {
		int index = sum[i] + counts;//加上偏置,因为sum可能小于零
		if (temp[index] == -1) {
			temp[index] = i;
		}
		else {
			int curlen = i - temp[index];
			if (curlen > maxlen) {
				startindex = temp[index];
				endindex = i - 1;
				maxlen = curlen;
			}
		}
	}
	if (maxlen != -1) {
		cout << "MAXLEN: " << maxlen << " START: " << startindex << " END: " << endindex << endl;
	}
	else {
		cout << "NOT EXIST" << endl;
	}
}



找数组中最长和为0连续子序列


输入:int 型数组由正数、负数、0组成

输出:最长和为0的子序列


例:

输入:[3,0,-1,-2,-3,1,1,1,2,3,1,-2,-1]

输出:9


思路:原数组为A,长度为N

          新建一个数组B[1...N+1],B[i]=A[i-1]+A[i-2]+A[1],B[1]=0             时间复杂度为O(n)

          将问题转化为求B数组两个相同数字最远距离                                时间复杂度可能为O(n^2)

	 private static int find(int[] arr) {
  		int[] arr1 = new int[arr.length + 1];
  		//创建求和串,为了考虑整个串和为0,将首项设置为0;
  		arr1[0] = 0;
  		for (int x = 0; x < arr.length; x++) {
   			arr1[x+1] = arr[x]+arr1[x];
  		}
  
  		int arr2[] = new int [arr.length];
  		//寻找两个相同数字最大距离,从后向前,找到就ok
  		for(int i = 0;i<arr.length;i++){
   			arr2[i]=0;
   			//剩下的如果没有已找到的长,就不找了
   			if(i!=0&&arr2[i-1]>arr.length-i){
    				break;
   			}
   			for(int j =arr.length;j>=0;j--){
    				if(arr1[i]==arr1[j]){
     					arr2[i]=j-i;
     					break;
    				}
   			}
  		}
  
  		//找最大值
  		int max = 0;
  		for(int x =0;x<arr2.length;x++){
   			if(arr2[x]>max){
   				max=arr2[x];
   			}
  		}    
  		return max;
 	}


POJ - 3061 Subsequence(连续子序列和>=s的最短子序列长度)

题意:T组实例,每组实例给出n和s,接着n个数。求连续子序列和大于等于s的最短子序列长度。

分析:有点点模拟的意思,形象点说,就像你堆积木,超过一定值S后我们就把最下面的几块积木去掉。具体方法是从前面开始不断累加,当和值超过S时减少前面的数值,然后记录下刚好>=S的ans值,如此重复直到序列尾,输出最小的ans。

 

AC代码1:

 

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<math.h>
 4 #include<queue>
 5 #include<algorithm>
 6 #include<time.h>
 7 #include<stack>
 8 using namespace std;
 9 #define N 1200000
10 #define INF 0x3f3f3f3f
11 
12 int dp[N];
13 int a[N];
14 
15 int main()
16 {
17     int n,m,T,i,j;
18 
19     scanf("%d", &T);
20 
21     while(T--)
22     {
23         scanf("%d %d", &n, &m);
24         memset(dp,0,sizeof(dp));
25 
26         for(i=1;i<=n;i++)
27         {
28             scanf("%d", &a[i]);
29             dp[i]=dp[i-1]+a[i];
30         }
31 
32         int minn=INF,i=1;;
33         for(j=1;j<=n;j++)
34         {
35              if(dp[j]-dp[i-1]<m)
36                 continue ;
37              while(dp[j]-dp[i]>=m)
38                 i++;
39              minn=min(minn,j-i+1);
40         }
41 
42       if(minn==INF)
43         printf("0\n");
44       else
45         printf("%d\n", minn);
46     }
47     return 0;
48 }
复制代码



最长回文子串

class Solution {
public:

string longestPalindrome(string str) {
    
    if(str.empty()) return "";
    int len=str.length();
    int min_start=0;
    int max_len=1;
    
    for(int i=0;i<len-1;){
        if(len-i<=max_len/2) break;
        int left=i;
        while(i<len-1&&str[i]==str[i+1]) i++;//跳过重复字符
        int right=i;
        i++;
        
        while(left>0&&right<len-1&&str[left-1]==str[right+1]){//往两边扩展
            left--;
            right++;
        }
        
        if(max_len<right-left+1){
            min_start=left;
            max_len=right-left+1;
        }
    }
    return str.substr(min_start,max_len);
}

};
 

最长无重复字串(不是子序列,而是连续的)

void LNRS_dp_hash_impro(char * arr, int size)
{
    memset(visit, -1, sizeof visit);
    maxlen = maxindex = 0;
    visit[arr[0]] = 0;
    int curlen = 1;
    int last_start = 0;
 
    for(int i = 1; i < size; ++i)
    {
        if(visit[arr[i]] == -1)
        {
            ++curlen;
            visit[arr[i]] = i; /* 记录字符下标 */
        }else
        {
            if(last_start <= visit[arr[i]])
            {
                curlen = i - visit[arr[i]];
                last_start = visit[arr[i]] + 1;
                visit[arr[i]] = i; /* 更新最近重复位置 */
            }else
            {
                ++curlen;
                visit[arr[i]] = i; /* 更新最近重复位置 */
            }
        }
        if(curlen > maxlen)
        {
            maxlen = curlen;
            maxindex = i + 1 - maxlen;
        }
    }
    output(arr);
}






最大乘积子数组

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n=nums.size();
        int posmax=nums[0];
        int negmax=nums[0];
        int res=nums[0];
        for(int i=1;i<n;i++){
            int postmp=posmax;
            int negtmp=negmax;
            
            posmax=max(nums[i],max(nums[i]*postmp,nums[i]*negtmp));
            negmax=min(nums[i],min(nums[i]*postmp,nums[i]*negtmp));
            
            res=max(res,posmax);
        }
        return res;
    }
};






#include<string>
#include<vector>
#include<iostream>
using namespace std;
//最长公共子序列 
int lcs1(string str1, string str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    
    vector<vector<int>>c(len1+1,vector<int>(len2+1));
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1[i-1] == str2[j-1]) {
                c[i][j] = c[i-1][j-1] + 1;
            } else {
                c[i][j] = max(c[i - 1][j], c[i][j - 1]);
            }
        }
    }
    return c[len1][len2];
}

//最长公共子串长度
int lcs2(string str1, string str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    int result = 0;     //记录最长公共子串长度
    vector<vector<int>>c(len1+1,vector<int>(len2+1));
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1[i-1] == str2[j-1]) {
                c[i][j] = c[i-1][j-1] + 1;
                result = max(c[i][j], result);
            } else {
                c[i][j] = 0;
            }
        }
    }
    return result;
}

int main(){
	string str1="cnblogs";
	string str2="belong";
	cout<<lcs1(str1,str2)<<" "<<lcs2(str1,str2)<<endl;
}






区间dp

题目链接:

http://poj.org/problem?id=1141

题目大意:

给一个由[,],{,}组成的字符串序列,求增加最少的字符,使该序列能够匹配,并输出最后的方案。

解题思路:

区间dp.dp[i][j]表示从i~j 所需的最少的字符使之能匹配,转移的话要么是头尾匹配直接加中间,要么分成两段。

不过要输出到达路径,所以在用一个path[i][j]表示到达该路径时的选择,-1表示头尾,其他表示中间分开的位置。

递归输出路径。递归是个好东西,能够很大程度的改变顺序,特别是逆着的。

PS:这题不能用贪心直接模拟过,不一定非要每步都与前面的都匹配。

测试样例:(([[)]]  长度应为10 用模拟为12 

代码:

[cpp]  view plain  copy
  1. <span style="font-size:14px;">//递归输出结果,是个好东西  
  2. #include<iostream>  
  3. #include<cmath>  
  4. #include<cstdio>  
  5. #include<cstdlib>  
  6. #include<string>  
  7. #include<cstring>  
  8. #include<algorithm>  
  9. #include<vector>  
  10. #include<map>  
  11. #include<set>  
  12. #include<stack>  
  13. #include<list>  
  14. #include<queue>  
  15. #define eps 1e-6  
  16. #define INF 0x1f1f1f1f  
  17. #define PI acos(-1.0)  
  18. #define ll __int64  
  19. #define lson l,m,(rt<<1)  
  20. #define rson m+1,r,(rt<<1)|1  
  21. //#pragma comment(linker, "/STACK:1024000000,1024000000")  
  22. using namespace std;  
  23.   
  24. /* 
  25. freopen("data.in","r",stdin); 
  26. freopen("data.out","w",stdout); 
  27. */  
  28. #define Maxn 110  
  29. char sa[Maxn];  
  30. int dp[Maxn][Maxn],path[Maxn][Maxn]; //dp[i][j]表示区间i~j内需要最少的字符数能够匹配,path[i][j]表示到达该状态是哪种情况,  
  31. //-1表示第一个和最后一个,其他表示中间的分段点,然后递归输出  
  32. //递归能够改变次序  
  33. void output(int l,int r) //递归是个好东西  
  34. {  
  35.    if(l>r)  
  36.       return ;  
  37.    if(l==r) //到达了最后  
  38.    {  
  39.       if(sa[l]=='('||sa[l]==')')  
  40.          printf("()");  
  41.       else  
  42.          printf("[]");  
  43.       return ;  
  44.    }  
  45.    if(path[l][r]==-1) //首尾,先输出开始,然后递归输出中间,最后输出结尾  
  46.    {  
  47.       putchar(sa[l]);  
  48.       output(l+1,r-1);  
  49.       putchar(sa[r]);  
  50.    }  
  51.    else  
  52.    {  
  53.       output(l,path[l][r]);//直接递归输出两部分  
  54.       output(path[l][r]+1,r);  
  55.    }  
  56. }  
  57. int main()  
  58. {  
  59.    while(gets(sa+1)) //有空串,scanf("%s"),不能读空串,然后少一个回车,会出错  
  60.    {  
  61.       int n=strlen(sa+1);  
  62.       memset(dp,0,sizeof(dp));  
  63.       for(int i=1;i<=n;i++)  
  64.          dp[i][i]=1; //一个的话只需一个就可以匹配  
  65.       for(int gap=1;gap<n;gap++) //枚举区间长度  
  66.          for(int i=1;i<=n-gap;i++) //枚举区间开始位置  
  67.          {  
  68.             int j=i+gap;  
  69.             dp[i][j]=INF;  
  70.             if((sa[i]=='['&&sa[j]==']')||(sa[i]=='('&&sa[j]==')')) //首尾情况  
  71.                if(dp[i+1][j-1]<dp[i][j])  
  72.                   dp[i][j]=dp[i+1][j-1],path[i][j]=-1;  
  73.             for(int k=i;k<j;k++) //中间分隔情况  
  74.                if(dp[i][k]+dp[k+1][j]<dp[i][j])  
  75.                   dp[i][j]=dp[i][k]+dp[k+1][j],path[i][j]=k;  
  76.          }  
  77.       output(1,n);  
  78.       putchar('\n');  
  79.    }  
  80.    return 0;  
  81. }  
  82. </span>  

HDU 4745 Two Rabbits

题目地址

题意
两只兔子,在n块围成一个环形的石头上跳跃,每块石头有一个权值ai,一只从左往右跳,一只从右往左跳,每跳一次,两只兔子所在的石头的权值都要相等,在一圈内(各自不能超过各自的起点,也不能再次回到起点)它们最多能经过多少个石头(1 <= n <= 1000, 1 <= ai <= 1000)。

分析
其实就是求一个环中,非连续最长回文子序列的长度。
dp[i][j] = max{ dp[i + 1][j], d[i][j - 1], (if a[i] == a[j]) dp[i + 1][j - 1] + 2 }

复制代码
 1 #include <iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<stdlib.h>
 6 using namespace std;
 7 int dp[1010][1010];
 8 int a[1010];
 9 int main()
10 {
11     int i,j,n;
12     while(scanf("%d",&n)!=EOF)
13     {
14         if(!n) break;
15         memset(dp,0,sizeof(dp));
16         for(i = 1; i <= n ; i++)
17         {
18             scanf("%d",&a[i]);
19             dp[i][i] = 1;
20         }
21         for(i = n ; i >= 1 ;i--)
22         {
23             for(j = i+1; j <= n ; j++)
24             {
25                 if(a[i]==a[j])
26                 dp[i][j] = dp[i+1][j-1]+2;
27                 dp[i][j] = max(dp[i][j],max(dp[i+1][j],dp[i][j-1]));
28             }
29         }
30         int ans=1;
31         for(i = 1; i < n ; i++)
32         ans = max(ans,dp[1][i]+dp[i+1][n]);
33         printf("%d\n",ans);
34     }
35     return 0;
36 }
复制代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值