题解:复制书稿

题目

题目描述

现在要把m本有顺序的书分给k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。
现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

输入格式

第一行两个整数 m,k。
第二行m个整数,第i个整数表示第i本书的页数。

输出格式

共k行,每行两个整数,第i行表示第i个人抄写的书的起始编号和终止编号。k行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

输入输出样例

输入

9 3
1 2 3 4 5 6 7 8 9

输出

1 5
6 7
8 9

解析

法一 二分

首先可以看到这道题里的关键词:最大值最小,所以果断想到二分。
二分还是非常简单的,重点在于输出路径:我们已经得到了答案,所以我从后往前枚举每本书的页数,当时间大于答案的时候就存一下左端点和右端点,重开一组

void print(int ans) {
    int tot = 0;
    int i = m;
    int pl = k;
    int lst = 0;
    path[k] = a[m].id;
    while(i >= 0) {
	    tot += a[i].num;
	    if(tot > ans) {
		    tot = a[i].num;
		    path[pl] = i + 1;
		    pl--;
	    }
	    i--;
	    if(i == 0 && tot <= ans) {
		    path[1] = 1;
	    }
    }
}

注意这种方法输出的时候要减一,因为记录的是多一个的端点

for(int i = 1; i <= k; i++) {
	printf("%d %d\n" ,path[i],path[i + 1] - 1);
}

代码

#include<bits/stdc++.h>
using namespace std;
struct book {
    int id,num;
} a[1000001];
int m,k;
int st = 0,ed = 0;
int tot = 0;
int ans = 0;
int path[1001];
int judge(int x) {
    int tot = 0;
    int group = 0;
    for(int i = m; i >= 1; i--) {
	    tot += a[i].num;
	    if(tot > x) {
		    tot = a[i].num;
		    group++;
	    }
	    if(tot == x) {
		    tot = 0;
		    group++;
	    }
    }
    if(group >= k) {
	    return 1;
    } else return 0;
}

void print(int ans) {
    int tot = 0;
    int i = m;
    int pl = k;
    int lst = 0;
    path[k] = a[m].id;
    while(i >= 0) {
	    tot += a[i].num;
	    if(tot > ans) {
		    tot = a[i].num;
		    path[pl] = i + 1;
		    pl--;
	    }
  	    i--;
	    if(i == 0 && tot <= ans) {
		    path[1] = 1;
	    }
    }
}

int main() {
    scanf("%d %d" ,&m,&k);
    for(int i = 1; i <= m; i++) {
	    scanf("%d" ,&a[i].num);
	    tot += a[i].num;
	    a[i].id = i;
    }
    ed = tot;
    while(st < ed) {
	    int mid = (st + ed) >> 1;
	    if(judge(mid) == 1) st = mid + 1;
	    else ed = mid;
    }
    ans = st;
    path[k + 1] = m + 1;
    print(ans);
    for(int i = 1; i <= k; i++) {
	    printf("%d %d\n" ,path[i],path[i + 1] - 1);
    }
    return 0;
}

/*
9 3
1 2 3 4 5 6 7 8 9
*/

法二 动态规划

dp[i][j]表示前i个分j段最大值的最小值
状态转移方程:

dp[i][k] = min(dp[i][k],max(dp[j][k - 1],sm(j + 1,i)));

其中sm表示j + 1 到 i的区间和,这个可以用前缀和优化一下

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e4 + 100;
const int mod = 1e6;
int n,m;
int a[N];
bool check(int mid) {
    int num = 0;
    ll tot = 0;
    for(int i = 1; i <= n; i++) {
	    if(a[i] > mid) return false;
	    if(tot + a[i] > mid) {
		    num++;
		    tot = 0;
	    }
	    tot += a[i];
    }
    if(tot) num++;
    return num <= m;
}
int l[N],r[N];
ll sum[N],dp[600][600];
ll sm(int l,int r) {
    return sum[r] - sum[l - 1];
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++) {
	    scanf("%d",&a[i]);
	    sum[i] = sum[i-1] + a[i];
	    dp[i][1] = sum[i];
    }
    for(int k = 2; k <= m; k++) {
	    for(int i = 1; i <= n; i++) {
		    dp[i][k] = 2e15;
		    for(int j = 1; j < i; j++) {
			    dp[i][k] = min(dp[i][k],max(dp[j][k - 1],sm(j + 1,i)));
		    }
	    }
    }
    int pl = n,id = m,rr = n,st = dp[n][m];
    ll tot = 0;
    while(pl >= 0) {
	    if(tot + a[pl] > st || pl == 0) {
		    l[id] = pl + 1;
		    r[id] = rr;
		    rr = pl;
		    id--;
		    tot = 0;
	    }
	    tot += a[pl--];
    }
    for(int i = 1; i <= m; i++) {
	    printf("%d %d\n",l[i],r[i]);
    }
    return 0;
}
posted @ 2021-06-17 19:54  24Kmagic  阅读(102)  评论(0)    收藏  举报