zoukankan      html  css  js  c++  java
  • poj1505 Copying Books ***

    /*
    * DP
    * d[i][j] : 前i个人,分完1~j本书,d值满足:minimize the maximum number of pages assigned to a single scriber
    * d[k][m]即为所求
    *
    * 状态转移方程:d[i][j] = min( max(d[i-1][t] , page[j] - page[t]) ) 其中: i-1 <= t <= j-1 (注意每个人至少一本书)
    * 其中 page[i] 为 1~i本书的总页数, page[j]-page[t] 即为第j个人分到的页数
    *
    * 最后注意满足 “If there is more than one solution,
    * print the one that minimizes the work assigned to the first scriber,
    * then to the second scriber etc. ”
    *
    *    其实dp本身不难,这个才有点费劲~
    *
    * 开始用了int型数组record记录划分状态,后来TLE , 后来改成bool型, WA 以下数据出了错误:
    * 5 3
    * 1 1 1 1 10
    * 答案是: 1 / 1 1 1 / 10
    *
    * 最后改成中间过程不记录, 直接算出答案, 再根据答案用贪心法逆向找出划分方法
    *
    */

    #include
    <cstdio>
    #include
    <cstring>
    using namespace std;

    const int inf = 1000000000;
    const int maxM = 500 + 5;
    int n, m, k, p[maxM];
    int d[maxM][maxM], page[maxM];
    //bool record[2][maxM][maxM]; record[][x][y]=1 表示: 分完1~x本书的情况中,第y本书后应该加‘/’。
    //。根据状态转移方程,只需记录两组数据

    int inline get_max(int lhs, int rhs){
    return (lhs > rhs ? lhs : rhs);
    }

    int main(){
    scanf(
    "%d", &n);
    while(n--){
    scanf(
    "%d %d", &m, &k);

    memset(d,
    0, sizeof(d));
    memset(page,
    0, sizeof(page));
    // memset(record, 0, sizeof(record));
    int last = 1, cur = 0;
    for(int i=1; i<=m; i++){
    scanf(
    "%d", &p[i]);
    page[i]
    = page[i-1] + p[i];
    d[
    1][i] = page[i];
    // record[cur][i][i] = 1;
    }

    for(int i=2; i<=k; i++){
    int tmp = cur; cur = last; last = tmp;
    for(int j=i; j<=m-k+i; j++){
    d[i][j]
    = inf;
    for(int t=i-1; t<=j-1; t++){
    int tmpMax = get_max(d[i-1][t], page[j]-page[t]);
    if(d[i][j] > tmpMax){
    d[i][j]
    = tmpMax;
    // memcpy(record[cur][j], record[last][t], sizeof(bool)*(t+1));
    // record[cur][j][j] = 1;
    }

    }
    }
    }

    //逆向找出划分方法
    bool record[maxM] = {};
    int tmpSum = 0, slashNum = 0;
    for(int i=m; i>=1; i--){
    if(tmpSum + p[i] > d[k][m]){
    record[i]
    = 1; slashNum++;
    tmpSum
    = p[i];
    }
    else tmpSum += p[i];
    }
    if(slashNum < k-1){ //把slash补足
    for(int i=1; i<=m && slashNum!=k-1; i++){
    if(record[i] != 1){
    record[i]
    = 1; slashNum++;
    }
    }
    }


    for(int i=1; i<m; i++){
    printf(
    "%d", p[i]);
    if(record[i]) printf(" / ");
    else printf(" ");
    }
    printf(
    "%d\n", p[m]);

    }

    return 0;
    }


    ——————————————————————————————————————————————————————————


    在网上又找到 二分查找+判定 的方法:


    //二分查找+判定 (思想很经典)

    #include
    <cstdio>
    #include
    <cstring>

    typedef __int64 llong;

    const int MAXN = 510;
    llong book[MAXN];
    bool use[MAXN];
    int N, K;

    llong Max(llong a, llong b){
    return a > b ? a : b;}

    int check(llong L)
    {
    int i, cnt;
    llong sum
    = 0;
    i
    = N - 1;
    //用来标记段数,对于不同的L值,都是先进行更新。
    memset(use, 0, sizeof(use));
    //段数
    cnt = 1;
    while (i >= 0)
    {
    //大于,则须在I处断开,即任何一段之和要小于L
    if (sum + book[i] > L)
    {
    //标记此处要段开
    use[i+1] = 1;
    //段数加1
    cnt++;
    //开始新的一段
    sum = book[i];
    }
    else
    {
    //小于,仍属于此段
    sum += book[i];
    }
    i
    --;
    }
    return cnt;
    }

    void solve()
    {
    int i;
    llong min, max, mid, cnt, sum;
    min
    = 0;
    sum
    = 0;
    scanf(
    "%d %d", &N, &K);
    //二分的起始点为[最小的页数 ,页数之和],因为最大值一定在这之间
    for (i = 0; i < N; i++)
    {
    scanf(
    "%I64d", &book[i]);
    sum
    += book[i];
    min
    = Max(min, book[i]);
    }
    max
    = sum;
    //二分查找
    while (min < max)
    {
    mid
    = (min + max) / 2;
    //如果以MID为最大值,而得到的段数小于等于K,说明MID值太大了
    if (check(mid) <= K)
    max
    = mid;
    //否则MID值太小,使得段数大于K
    else
    min
    = mid + 1;
    }
    //求出以MAX为最大值所能够得到的段数。(从后至前,因为题目要求使得前面的任务越小越好)
    cnt = check(max);
    for (i = 1; i < N && cnt < K; i++)
    {
    //多余的段数全部用在最前面,使得前面的工人任务数是最优解中最少的
    if (!use[i])
    {
    use[i]
    = true;
    cnt
    ++;
    }
    }
    for (i = 0; i < N; i++)
    {
    printf(
    "%I64d ", book[i]);
    if (use[i+1])
    printf(
    "/ ");
    }
    printf(
    "\n");
    }

    int main()
    {
    int t;
    scanf(
    "%d", &t);
    while (t--)
    solve();
    return 0;
    }

    ——————————————————————————————————————————————————————————————————————————————

    附:discuss :

    二分+贪心需要注意的几个地方:
    贪心:题目要求划分的区间编号字典序最小,因此需要从右向左贪心,若当前区间和>二分枚举值maxs 则区间数+1

    判断可行性时,
    1.if book[i]>maxs return false
    2.只要是需要的区间个数<=m 即return true

    二分结束后,
    1.再执行一次judge过程,以便pos数组保存的是最终的结果
    2.从小到大,将区间个数补足m个

  • 相关阅读:
    如何分析页面性能?
    Java io包 ByteArrayInputStream&ByteArrayOutStream
    Java io包 inputstream&outputstream
    Java executors创建线程池和使用ThreadPoolExecutor
    Android异步任务处理
    TCP报文格式
    Java 异常
    死锁
    计算机网络-传输层
    Linux 进程同步和通信
  • 原文地址:https://www.cnblogs.com/longdouhzt/p/2119816.html
Copyright © 2011-2022 走看看