poj 1505 dp(数列分段,最大段和最小)

题意:给出一个有n个数的数列,要求把这n个数分成m段,不能改变原数列的顺序。每段至少一个数。求使得加和最大的那段的和最小的划分方案。如果有多组解的话先要保证第一段和尽量小,若仍有多组解,要先保证第二段和尽量小,以此类推。(与poj3273题意相同,只是需要输出答案)

思路:dp。dp[i][j]表示将前j个数分成i段的段和最大的最小值。一开始dp过程中记录路径然后输出,结果wa。分析原因在于输出不符合题意,比如数据:

5 3
1 1 1 1 10

如果利用dp记录的话输出为1 1 / 1 1 / 10,而正确的输出应该为:1 / 1 1 1 / 10。原因在于前四个数分成两段的最优值不是整个输出的最优值。注意到最优值的结果是不变的(dp[m][n]),所以拿最优值的结果贪心输出最优解即可,贪心的过程就是从后向前,每次取不超过dp[m][n]值的最大的组合(注意剩出足够的保证每段至少有一个数)。

还有其他做法二分+贪心输出(与前述输出方法一样)。二分查找这个加和最大的段的加和最小值。在查找过程中,每次枚举这个加和,对数列从左到右看一遍,看在每段加和不超过这个枚举值的前提下最少可以将这个数列分成几段。如果分段数小于等于m则这个枚举值偏大或者刚刚好,如果大于m则说明枚举值偏小。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 505
int dp[N][N],s[N];
int T,n,m;
void print(int m,int n,int now,int res){//m表示是当前
    if(!n)
        return;
    if(now+s[n] > res || m>n){//如果段长超过最优值或者剩下的不够每段至少一个数,那么此段输出完毕。
        print(m-1,n,0,res);
        printf("/ ");
    }else{
        print(m,n-1,now+s[n],res);
        printf("%d ",s[n]);
    }
}
int main(){
    scanf("%d",&T);
    while(T--){
        int i,j,k,res,now,a;
        scanf("%d %d",&n,&m);
        dp[1][0] = 0;
        for(i = 1;i<=n;i++){
            scanf("%d",&s[i]);
            dp[1][i] = dp[1][i-1]+s[i];
        }
        for(i = 2;i<=m;i++)
            for(j = i;j<=n;j++){
                for(k = j,now=0,a=0x3fffffff;k>=i;k--){
                    now += s[k];
                    if(max(now,dp[i-1][k-1]) <= a)
                        a = max(now,dp[i-1][k-1]);
                    else
                        break;
                }
                dp[i][j] = a;
            }
        print(m,n,0,dp[m][n]);
        putchar('\n');
    }
    return 0;
}

采用3273二分的思路:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 505
int dp[N][N],s[N];
int T,n,m;
void print(int m,int n,int now,int res){//m表示是当前
    if(!n)
        return;
    if(now+s[n] > res || m>n){//如果段长超过最优值或者剩下的不够每段至少一个数,那么此段输出完毕。
        print(m-1,n,0,res);
        printf("/ ");
    }else{
        print(m,n-1,now+s[n],res);
        printf("%d ",s[n]);
    }
}
int main(){
    scanf("%d",&T);
    while(T--){
        int i,j,k,low,high,mid,num,sum;
        scanf("%d %d",&n,&m);
        low = high = 0;
        for(i = 1;i<=n;i++){
            scanf("%d",&s[i]);
            low = max(low,s[i]);//关键点之一,low如果从0开始则会出现错误
            high += s[i];
        }
        while(low <= high){
            mid = (low+high)>>1;
            num = sum = 0;
            for(i = 1;i<=n;i++){
                sum += s[i];
                if(sum > mid){
                    num ++;
                    sum = s[i];
                }
            }
            num++;//加上最后一天的组
            if(num > m)
                low = mid+1;
            else
                high = mid-1;
        }
        print(m,n,0,low);
        putchar('\n');
    }
    return 0;
}


你可能感兴趣的:(poj 1505 dp(数列分段,最大段和最小))