ACM第一次集训 - 动态规划问题

2018-4-15

1.石子合并问题
1)设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=100)。每堆沙子有一定的数量。现要将N堆沙子并成为一堆。归并的过程每次将任意两堆沙子堆成一堆(每次合并花费的代价为当前两堆沙子的总数量),这样经过N-1次归并后成为一堆,归并的总代价为每次合并花费的代价和。找出一种合理的归并方法,使总的代价最小。

我们可以使用贪心算法,每次只合并两个数量最小的沙子,所得到的结果就是最优解了。

其实这就是哈夫曼编码的变形。

每一次都要进行排序,虽然有点智障,但是还是写了…(注释的地方),
我的想法是每次将合并过后的值插入到它合适的位置,使得剩下的还是有序的即可。

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 100;
int x[N+1];
int n;

int main(){
    int i,j;
    while (cin>>n){
        int sum=0;
        for (i=0;i<n;i++){
            cin>>x[i];
        }
        sort(x,x+n);
        // for (i=1;i<n;i++){
        //     sum+=x[i]+x[i-1];
        //     x[i]=x[i]+x[i-1];
        //     sort(x+i,x+n);
        // }
        for (i=1;i<n;i++){
            int tmp=x[i]+x[i-1];
            sum+=tmp;
            j=i+1;
            while (tmp>x[j]&&j<n){
                x[j-1]=x[j];
                j++;
            }
            x[j-1]=tmp;
        }
        cout<<sum<<endl;
    }
    return 0;
}

2)倘若我们每次只能合并相邻的两个石子呢?
贪心这个时候就不可以了,因为我们无法保证全局最优,虽然不对,但是我还是写了…

#include<iostream>
#include<vector>
#define inf 0x3f3f3f3f
using namespace std;

int n;

int main(){
    vector<int>vec;
    while (cin>>n){
        int a;
        for (int i=0;i<n;i++){
            cin>>a;
            vec.push_back(a);
        }
        int cnt=0;
        for (int i=0;i<n-1;i++){
            int tmp=inf,k=0;
            for (int j=1;j<vec.size();j++){
                if (vec[j]+vec[j-1]<tmp){
                    tmp=vec[j]+vec[j-1];
                    k=j;
                }
            }
            cnt+=tmp;
            vec.erase(vec.begin()+k);
            vec.erase(vec.begin()+k-1);
            vec.insert(vec.begin()+k-1,tmp);
        }   
        cout<<cnt<<endl;
    }
    return 0;
}

其实正确的解法应该是用动态规划求解:

#include<iostream>
using namespace std;

const int N = 1000;
int x[N+1],dp[N+1][N+1],s[N+1];
int n;

int main(){
    while (cin>>n){
        for (int i=1;i<=n;i++){
            cin>>x[i];
            s[i]=s[i-1]+x[i];
        }
        for (int l=2;l<=n;l++){
            for (int i=1;i<=n-l+1;i++){
                int j=i+l-1;
                dp[i][j]=N;
                for (int k=i;k<j;k++){
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+s[j]-s[i]+x[i];
                }
            }
        }
//      for (int i=1;i<=n;i++){
//          for (int j=1;j<=n;j++){
//              cout<<dp[i][j]<<" ";
//          }
//          cout<<endl;
//      }
        cout<<dp[1][n]<<endl;
    }
    return 0;
}

3)其实最终的题目应该是我们的这个沙子堆是环形的!

其实对于环形问题我们之前有过涉及,就是在直线后面再补充那么多就可以了!就是与USACO里面的那个项链问题差不多!

#include<iostream>
using namespace std;

const int N = 2000;
int x[N+1],dp[N+1][N+1],s[N+1];
int n;

int main(){
    while (cin>>n){
        for (int i=1;i<=n;i++){
            cin>>x[i];
            s[i]=s[i-1]+x[i];
        }
        for (int i=1;i<=n;i++){
            x[i+n]=x[i];
            s[i+n]=s[i+n-1]+x[i];
        }
        for (int l=2;l<=n;l++){ //最多还是合并 n 堆沙石子 
            for (int i=1;i<=2*n-l+1;i++){
                int j=i+l-1;
                dp[i][j]=N;
                for (int k=i;k<j;k++){
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
                }
                dp[i][j]+=s[j]-s[i]+x[i];
            }
        }
        int res=N;
        for (int i=1;i<=n;i++){
            res=min(res,dp[i][i+n-1]);
        } 
        cout<<res<<endl;
    }
    return 0;
}

2.整数划分问题
自行车选手在训练时,需要围绕着场地骑行N圈。给了使得训练有效,可以一次把N全部骑完,
也可以分成若干次完成,但每次都比上一次骑的圈数要多,那么完成一次训练即骑完N圈,
有多种训练方式

样例输入
6

样例输出
4

//例如,当 N = 6 时,有以下四种训练方案:
6
1 2 3
1 5
2 4

这个题目其实就是一个整数划分问题,整数划分又分为好多种:
1)将n划分成若干正整数之和的划分数
其实这是一个方苹果的问题,假设f(n,m)表示将n个苹果放在m个篮子里面,那么我们一共有两种情况:如果n>m的话,我们一定会有空的篮子,也就是等同于f(m,m),反之n

#include<iostream>
using namespace std;

int n,cnt;

void dfs(int sum,int pre){
    if (sum>n) return ;
    if (sum==n){
        cnt++;
        return ;
    }
    for (int i=pre;i<=n;i++){
        dfs(sum+i,i+1);
    }
}

int main(){
    while (cin>>n){
        cnt=0;
        dfs(0,1);
        cout<<cnt<<endl;
    }
    return 0;
}

实际上应该是用动态规划问题…

3.最长不下降子序列问题
若干人排成一行,且身高分别为b1,b2,…,bn。准备从中选出一组满足身高不降的人组成一队。

例如13,7,9,16,38,24,37,18,44,19,21,22,63,15。
有13<16<38<44<63 长度为5的不下降子序列。
但经过观察,实际还有7<9<16<18<19<21<22<63 长度为8的不下降子序列。
给出最长队列的长度。

【输入格式】

第一行为n,表示n(n≤100000)个数。第二行为n个数的值。

【输出格式】

一个整数。

【输入样例】

4

1 3 1 2

【输出样例】

2

简单的动态规划问题:

#include<iostream>
using namespace std;

const int N = 100000;
int x[N+1],dp[N+1];
int n;

int main(){
    while (cin>>n){
        for (int i=1;i<=n;i++){
            cin>>x[i];
        }
        for (int i=1;i<=n;i++){
            dp[i]=1;
            for (int j=1;j<i;j++){
                if (x[j]<=x[i]){
                    dp[i]=max(dp[j]+1,dp[i]);
                }
            }
        }
        int res=0;
        for (int i=1;i<=n;i++){
            res=max(res,dp[i]);
        }
        cout<<res<<endl;
    }
    return 0;
}

4.最长不下降子序列的升级问题
为了外来导弹袭击,最新的导弹拦截系统已经研制好了.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到了导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000 的正整数),计算这套系统最多能拦截多少导弹和如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

【输入样例】

389 207 155 300 299 170 158 65

【输出样例】

6(最多能拦截的导弹数)

2(要拦截所有导弹最少要配备的系统数)

#include<iostream>
using namespace std;

const int N = 100000;
int x[N+1],dp[N+1];
int n;

int main(){
    int i=0;
    while (cin>>x[i]){
        i++;
        if (getchar()=='\n') break;
    }
    n=i;
    for (int i=1;i<=n;i++){
        dp[i]=1;
        for (int j=1;j<i;j++){
            if (x[j]>=x[i]){
                dp[i]=max(dp[j]+1,dp[i]);
            }
        }
    }
    int res=0;
    for (int i=1;i<=n;i++){
        res=max(res,dp[i]);
    }
    cout<<res<<endl;
    for (int i=1;i<=n;i++){
        dp[i]=1;
        for (int j=1;j<i;j++){
            if (x[j]<=x[i]){
                dp[i]=max(dp[j]+1,dp[i]);
            }
        }
    }
    res=0;
    for (int i=1;i<=n;i++){
        res=max(res,dp[i]);
    }
    cout<<res<<endl;
    return 0;
}

这里需要我们用一个结论:求…等于最长非递增子序列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值