线性DP

271. 杨老师的照相排列

题意:

有n个学生根据身高排队合影,一种站成k行,每行左对齐,要求左边高于右边,前面高于后面

思路:

假设我们将每个学生的身高从高到底排序,对于每个排列状态(a,b,c,d,e),都可以由最后一个加入的同学转移过来。

代码:

#include<iostream>
#include<string.h>
using namespace std;
typedef long long LL;
LL a[6];
LL f[31][31][31][31][31];
int main(){
    int n;
    while(cin>>n){
        if(!n) break;
        memset(a,0,sizeof a);
        memset(f,0,sizeof f);
        for(int i=1;i<=n;++i) cin>>a[i];
        f[0][0][0][0][0]=1;
        for(int a1=0;a1<=a[1];++a1){   //最后一行
            for(int a2=0;a2<=min(1ll*a1,a[2]);++a2){
                for(int a3=0;a3<=min(1ll*a2,a[3]);++a3){
                    for(int a4=0;a4<=min(1ll*a3,a[4]);++a4){
                        for(int a5=0;a5<=min(1ll*a4,a[5]);++a5){
                            LL v=f[a1][a2][a3][a4][a5];
                            if(a1<a[1]){
                                f[a1+1][a2][a3][a4][a5]+=v;
                            }
                            if(a2<a[2]&&a2+1<=a1){
                                f[a1][a2+1][a3][a4][a5]+=v;
                            }
                            if(a3<a[3]&&a3+1<=a2){
                                f[a1][a2][a3+1][a4][a5]+=v;
                            }
                            if(a4<a[4]&&a4+1<=a3){
                                f[a1][a2][a3][a4+1][a5]+=v;
                            }
                            if(a5<a[5]&&a5+1<=a4){
                                f[a1][a2][a3][a4][a5+1]+=v;
                            }
                        }
                    }
                }
            }
        }
        cout<<f[a[1]][a[2]][a[3]][a[4]][a[5]]<<endl;
    }
    return 0;
}

273. 分级

题意:

给定长度为N的序列A,构造一个长度为N的序列B,满足:

1、B非严格单调,即(B_1≤B_2≤…≤B_N)(B_1≥B_2≥…≥B_N)
2、最小化 (S=|A_i−B_i|)

只需要求出这个最小值S。

思路:

一定存在一种最优解,B序列中出现的数字都是来自于A序列的。f[i,j]表示构造长度为i的B序列,第i个数字是降序排序后的(a_j),那么(f[i,j]=min(f[i-1][1)$j])+abs(a[i]-b[j])$,复杂度O(n^3)。可以开一个临时数组距离f[i-1][1j]的最小值,val变量随着第i维一起遍历。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int f[N][N],tmp[N],a[N],b[N];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i],b[i]=a[i];
    sort(b+1,b+1+n);
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;++i){
        int val=tmp[1];
        for(int j=1;j<=n;++j){
            val=min(tmp[j],val);
            f[i][j]=min(f[i][j],val+abs(a[i]-b[j]));
            
        }
        memcpy(tmp,f[i],sizeof tmp);
    }
    int res=0x3f3f3f3f;
    for(int j=1;j<=n;++j) res=min(res,f[n][j]);
    cout<<res<<endl;
    return 0;
}

277. 饼干

题意:

有m块饼干分给n个小朋友,每个小朋友至少分到一块。有个小朋友都有一个贪婪度值(a_i),当一个小朋友比k个小朋友得到的饼干少,他就会产生(a_i*k)的怒气值。
求使得所有小朋友产生的最少怒气值和以及输出该分配方案。

思路:

这道题没有告诉分配顺序,但是DP问题首先要确定遍历顺序,用贪心的微调法可以得到一个结论,当(a_i>a_j),那么第i个小朋友就一定大于等于第j个小朋友分到的饼干。问题就转化成了将整数m分成n个正整数之和,这里利用900. 整数划分的分析方法,通过枚举当前状态里有至少有几个1转移。f[i][j]表示前i个小朋友分j块饼干的最小怒气值.

方案我们可以通过倒推得到。

代码:

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
PII a[40];
int f[40][5010],sum[40],ans[40];
int main(){
    int n,m;
    cin>>n>>m;
    int tm=m;
    for(int i=1;i<=n;++i) cin>>a[i].x,a[i].y=i;
    sort(a+1,a+1+n);
    reverse(a+1,a+1+n);
    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i].x;
    memset(f,0x3f,sizeof f);
    f[0][0]=0;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(j<i) continue;
            f[i][j]=f[i][j-i]; //0个1
            for(int k=1;k<=i;++k){
                f[i][j]=min(f[i][j],f[i-k][j-k]+(i-k)*(sum[i]-sum[i-k]));
            }
        }
    }
    cout<<f[n][m]<<endl;
    int i=n,j=m,h=0;
    while(i){
        if(j>=i&&f[i][j]==f[i][j-i]) j-=i,h++;
        else {
            for(int k=1;k<=i&&k<=j;++k){
                if(f[i][j]==f[i-k][j-k]+(i-k)*(sum[i]-sum[i-k])){
                    for(int l=i-k+1;l<=i;++l)
                        ans[a[l].y]=h+1;
                    i-=k;j-=k;
                    break;
                }
            }
        }
    }
    for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
    return 0;
}
原文地址:https://www.cnblogs.com/jjl0229/p/12691573.html