Open In App

Optimal read list for given number of days

Improve
Improve
Like Article
Like
Save
Share
Report

A person is determined to finish the book in ‘k’ days but he never wants to stop a chapter in between. Find the optimal assignment of chapters, such that the person doesn’t read too many extra/less pages overall.  

Example:

Input:  Number of Days to Finish book = 2
        Number of pages in chapters = {10, 5, 5}
Output: Day 1:  Chapter 1
        Day 2:  Chapters 2 and 3

Input:  Number of Days to Finish book = 3
        Number of pages in chapters = {8, 5, 6, 12}
Output: Day 1:  Chapter 1
        Day 2:  Chapters 2 and 3 
        Day 2:  Chapter 4

The target is to minimize the sum of differences between the pages read on each day and average number of pages. If the average number of pages is a non-integer, then it should be rounded to closest integer. In above example 2, average number of pages is (8 + 5 + 6 + 12)/3 = 31/3 which is rounded to 10. So the difference between average and number of pages on each day for the output shown above is “abs(8-10) + abs(5+6-10) + abs(12-10)” which is 5. The value 5 is the optimal value of sum of differences.

Recommended Practice

Consider the example 2 above where a book has 4 chapters with pages 8, 5, 6 and 12. User wishes to finish it in 3 days. The graphical representation of the above scenario is, 

BookChapter1 

In the above graph vertex represents the chapter and an edge e(u, v) represents number of pages to be read to reach ‘v ‘ from ‘u ‘. Sink node is added to symbolize the end of book. 

First, calculate the average number of pages to read in a day (here 31/3 roughly 10). New edge weight e ‘(u, v) would be the mean difference |avg – e(u, v)|. Modified graph for the above problem would be, 

BookChapter2 

Thanks to Armadillo for initiating this thought in a comment. The idea is to start from chapter 1 and do a DFS to find sink with count of edges being ‘k ‘. Keep storing the visited vertices in an array say ‘path[]’. If we reach the destination vertex, and path sum is less than the optimal path update the optimal assignment optimal_path[]. Note, that the graph is DAG thus there is no need to take care of cycles during DFS.

Following, is the C++ implementation of the same, adjacency matrix is used to represent the graph. The following program has mainly 4 phases. 

  1. Construct a directed acyclic graph. 
  2. Find the optimal path using DFS. 
  3. Print the found optimal path. 

Implementation:

C++




// C++ DFS solution to schedule chapters for reading in
// given days
# include <iostream>
# include <cstdlib>
# include <climits>
# include <cmath>
using namespace std;
 
// Define total chapters in the book
// Number of days user can spend on reading
# define CHAPTERS 4
# define DAYS 3
# define NOLINK -1
 
// Array to store the final balanced schedule
int optimal_path[DAYS+1];
 
// Graph - Node chapter+1 is the sink described in the
//         above graph
int DAG[CHAPTERS+1][CHAPTERS+1];
 
// Updates the optimal assignment with current assignment
void updateAssignment(int* path, int path_len);
 
// A DFS based recursive function to store the optimal path
// in path[] of size path_len.  The variable sum stores sum of
// of all edges on current path.  k is number of days spent so
// far.
void assignChapters(int u, int* path, int path_len, int sum, int k)
{
    static int min = INT_MAX;
 
    // Ignore the assignment which requires more than required days
    if (k < 0)
        return;
 
    // Current assignment of chapters to days
    path[path_len] = u;
    path_len++;
 
    // Update the optimal assignment if necessary
    if (k == 0 && u == CHAPTERS)
    {
        if (sum < min)
        {
            updateAssignment(path, path_len);
            min = sum;
        }
    }
 
    // DFS - Depth First Search for sink
    for (int v = u+1; v <= CHAPTERS; v++)
    {
        sum += DAG[u][v];
        assignChapters(v, path, path_len, sum, k-1);
        sum -= DAG[u][v];
    }
}
 
// This function finds and prints optimal read list.  It first creates a
// graph, then calls assignChapters().
void minAssignment(int pages[])
{
    // 1) ............CONSTRUCT GRAPH.................
    // Partial sum array construction S[i] = total pages
    // till ith chapter
    int avg_pages = 0, sum = 0, S[CHAPTERS+1], path[DAYS+1];
    S[0] = 0;
 
    for (int i = 0; i < CHAPTERS; i++)
    {
        sum += pages[i];
        S[i+1] = sum;
    }
 
    // Average pages to be read in a day
    avg_pages = round(sum/DAYS);
 
    /* DAG construction vertices being chapter name &
     * Edge weight being |avg_pages - pages in a chapter|
     * Adjacency matrix representation  */
    for (int i = 0; i <= CHAPTERS; i++)
    {
        for (int j = 0; j <= CHAPTERS; j++)
        {
            if (j <= i)
                DAG[i][j] = NOLINK;
            else
            {
                sum = abs(avg_pages - (S[j] - S[i]));
                DAG[i][j] = sum;
            }
        }
    }
 
    // 2) ............FIND OPTIMAL PATH................
    assignChapters(0, path, 0, 0, DAYS);
 
    // 3) ..PRINT OPTIMAL READ LIST USING OPTIMAL PATH....
    cout << "Optimal Chapter Assignment :" << endl;
    int ch;
    for (int i = 0; i < DAYS; i++)
    {
        ch = optimal_path[i];
        cout << "Day" << i+1 << ": " << ch << " ";
        ch++;
        while ( (i < DAYS-1 && ch < optimal_path[i+1]) ||
                (i == DAYS-1 && ch <= CHAPTERS))
        {
           cout <<  ch << " ";
           ch++;
        }
        cout << endl;
    }
}
 
// This function updates optimal_path[]
void updateAssignment(int* path, int path_len)
{
    for (int i = 0; i < path_len; i++)
        optimal_path[i] = path[i] + 1;
}
 
// Driver program to test the schedule
int main(void)
{
    int pages[CHAPTERS] = {7, 5, 6, 12};
 
    // Get read list for given days
    minAssignment(pages);
 
    return 0;
}


Java




// Java DFS solution to schedule chapters for reading in
// given days
import java.util.Arrays;
 
public class ChapterScheduler
{
 
  // Define total chapters in the book
  // Number of days user can spend on reading
  private static final int CHAPTERS = 4;
  private static final int DAYS = 3;
  private static final int NOLINK = -1;
 
  // Array to store the final balanced schedule
  private static final int[] optimal_path
    = new int[DAYS + 1];
 
  // Graph - Node chapter+1 is the sink described in the
  //         above graph
  private static final int[][] DAG
    = new int[CHAPTERS + 1][CHAPTERS + 1];
 
  // Updates the optimal assignment with current
  // assignment
  private static void updateAssignment(int[] path,
                                       int path_len)
  {
    for (int i = 0; i < path_len; i++)
      optimal_path[i] = path[i] + 1;
  }
 
  // A DFS based recursive function to store the optimal
  // path in path[] of size path_len. The variable sum
  // stores sum of all edges on current path. k is
  // number of days spent so far.
  private static void assignChapters(int u, int[] path,
                                     int path_len,
                                     int sum, int k)
  {
    final int min = Integer.MAX_VALUE;
 
    // Ignore the assignment which requires more than
    // required days
    if (k < 0)
      return;
 
    // Current assignment of chapters to days
    path[path_len] = u;
    path_len++;
 
    // Update the optimal assignment if necessary
    if (k == 0 && u == CHAPTERS) {
      if (sum < min) {
        updateAssignment(path, path_len);
      }
    }
 
    // DFS - Depth First Search for sink
    for (int v = u + 1; v <= CHAPTERS; v++) {
      sum += DAG[u][v];
      assignChapters(v, path, path_len, sum, k - 1);
      sum -= DAG[u][v];
    }
  }
 
  // This function finds and prints optimal read list. It
  // first creates a graph, then calls assignChapters().
  public static void minAssignment(int[] pages)
  {
    // 1) ............CONSTRUCT GRAPH.................
    int avg_pages = 0, sum = 0;
    int[] S = new int[CHAPTERS + 1];
    int[] path = new int[DAYS + 1];
    S[0] = 0;
 
    // Partial sum array construction S[i] = total pages
    // till ith chapter
    for (int i = 0; i < CHAPTERS; i++) {
      sum += pages[i];
      S[i + 1] = sum;
    }
 
    // Average pages to be read in a day
    avg_pages = Math.round(sum / DAYS);
 
    // DAG construction vertices being chapter name &
    // Edge weight being |avg_pages - pages in a
    // chapter| Adjacency matrix representation
    for (int i = 0; i <= CHAPTERS; i++) {
      for (int j = 0; j <= CHAPTERS; j++) {
        if (j <= i) {
          DAG[i][j] = NOLINK;
        }
        else {
          sum = Math.abs(avg_pages
                         - (S[j] - S[i]));
          DAG[i][j] = sum;
        }
      }
    }
 
    // 2) ............FIND OPTIMAL PATH................
    assignChapters(0, path, 0, 0, DAYS);
 
    // 3) ..PRINT OPTIMAL READ LIST USING OPTIMAL
    // PATH....
    System.out.println("Optimal Chapter Assignment :");
    int ch = 1;
    for (int i = 0; i < DAYS; i++) {
      System.out.print("Day" + (i + 1) + ": ");
      int sumPages = 0;
      while (ch <= CHAPTERS
             && (sumPages + pages[ch - 1])
             <= avg_pages * (i + 1)) {
        sumPages += pages[ch - 1];
        System.out.print(ch + " ");
        ch++;
      }
      System.out.println();
    }
  }
  public static void main(String[] args)
  {
    int[] pages = { 7, 5, 6, 12 };
 
    // Get read list for given days
    minAssignment(pages);
  }
}
 
// This code is contributed by rutikbhosale


Python3




# Python3 DFS solution to schedule chapters
# for reading in given days
 
# A DFS based recursive function to store
# the optimal path in path[] of size path_len.
# The variable Sum stores Sum of all edges on
# current path. k is number of days spent so far.
def assignChapters(u, path, path_len, Sum, k):
    global CHAPTERS, DAYS, NOLINK, optical_path, DAG, Min
 
    # Ignore the assignment which requires
    # more than required days
    if (k < 0):
        return
 
    # Current assignment of chapters to days
    path[path_len] = u
    path_len += 1
 
    # Update the optimal assignment if necessary
    if (k == 0 and u == CHAPTERS):
        if (Sum < Min):
            updateAssignment(path, path_len)
            Min = Sum
 
    # DFS - Depth First Search for sink
    for v in range(u + 1, CHAPTERS + 1):
        Sum += DAG[u][v]
        assignChapters(v, path, path_len, Sum, k - 1)
        Sum -= DAG[u][v]
 
# This function finds and prints
# optimal read list. It first creates a
# graph, then calls assignChapters().
def MinAssignment(pages):
    global CHAPTERS, DAYS, NOLINK, optical_path, DAG, MIN
     
    # 1) ............CONSTRUCT GRAPH.................
    # Partial Sum array construction S[i] = total pages
    # till ith chapter
    avg_pages = 0
    Sum = 0
    S = [None] * (CHAPTERS + 1)
    path = [None] * (DAYS + 1)
    S[0] = 0
 
    for i in range(CHAPTERS):
        Sum += pages[i]
        S[i + 1] = Sum
 
    # Average pages to be read in a day
    avg_pages = round(Sum/DAYS)
 
    # DAG construction vertices being chapter name &
    # Edge weight being |avg_pages - pages in a chapter|
    # Adjacency matrix representation
    for i in range(CHAPTERS + 1):
        for j in range(CHAPTERS + 1):
            if (j <= i):
                DAG[i][j] = NOLINK
            else:
                Sum = abs(avg_pages - (S[j] - S[i]))
                DAG[i][j] = Sum
 
    # 2) ............FIND OPTIMAL PATH................
    assignChapters(0, path, 0, 0, DAYS)
 
    # 3) ..PROPTIMAL READ LIST USING OPTIMAL PATH....
    print("Optimal Chapter Assignment :")
    ch = None
    for i in range(DAYS):
        ch = optimal_path[i]
        print("Day", i + 1, ": ", ch, end = " ")
        ch += 1
        while ((i < DAYS - 1 and ch < optimal_path[i + 1]) or
               (i == DAYS - 1 and ch <= CHAPTERS)):
            print(ch, end = " ")
            ch += 1
        print()
 
# This function updates optimal_path[]
def updateAssignment(path, path_len):
    for i in range(path_len):
        optimal_path[i] = path[i] + 1
 
# Driver Code
 
# Define total chapters in the book
# Number of days user can spend on reading
CHAPTERS = 4
DAYS = 3
NOLINK = -1
 
# Array to store the final balanced schedule
optimal_path = [None] * (DAYS + 1)
 
# Graph - Node chapter+1 is the sink
#          described in the above graph
DAG = [[None] * (CHAPTERS + 1)
       for i in range(CHAPTERS + 1)]
 
Min = 999999999999
pages = [7, 5, 6, 12]
 
# Get read list for given days
MinAssignment(pages)
 
# This code is contributed by PranchalK


C#




using System;
 
public class ChapterScheduler {
    // Define total chapters in the book
    // Number of days user can spend on reading
    private static readonly int CHAPTERS = 4;
    private static readonly int DAYS = 3;
    private static readonly int NOLINK = -1;
 
    // Array to store the final balanced schedule
    private static readonly int[] optimal_path = new int[DAYS + 1];
 
    // Graph - Node chapter+1 is the sink described in the
    // above graph
    private static readonly int[][] DAG = new int[CHAPTERS + 1][];
 
    static ChapterScheduler(){
        for (int i = 0; i <= CHAPTERS; i++) {
            DAG[i] = new int[CHAPTERS + 1];
        }
    }
 
    // Updates the optimal assignment with current
    // assignment
    private static void updateAssignment(int[] path, int path_len) {
        for (int i = 0; i < path_len; i++) {
            optimal_path[i] = path[i] + 1;
        }
    }
    // A DFS based recursive function to store the optimal
    // path in path[] of size path_len. The variable sum
    // stores sum of all edges on current path. k is
    // number of days spent so far.
    private static void assignChapters(int u, int[] path, int path_len, int sum, int k){
        int min = int.MaxValue;
 
        // Ignore the assignment which requires more than
        // required days
        if (k < 0) return;
 
        // Current assignment of chapters to days
        path[path_len] = u;
        path_len++;
 
        // Update the optimal assignment if necessary
        if (k == 0 && u == CHAPTERS) {
            if (sum < min) {
                updateAssignment(path, path_len);
            }
        }
 
        // DFS - Depth First Search for sink
        for (int v = u + 1; v <= CHAPTERS; v++) {
            sum += DAG[u][v];
            assignChapters(v, path, path_len, sum, k - 1);
            sum -= DAG[u][v];
        }
    }
 
    // This function finds and prints optimal read list. It
    // first creates a graph, then calls assignChapters().
    public static void minAssignment(int[] pages){
        // 1) ............CONSTRUCT GRAPH.................
        int avg_pages = 0, sum = 0;
        int[] S = new int[CHAPTERS + 1];
        int[] path = new int[DAYS + 1];
        S[0] = 0;
 
        for (int i = 0; i < CHAPTERS; i++) {
            sum += pages[i];
            S[i + 1] = sum;
        }
 
        // Average pages to be read in a day
        avg_pages = (int)Math.Round((double)sum / DAYS);
        // DAG construction vertices being chapter name &
        // Edge weight being |avg_pages - pages in a
        // chapter| Adjacency matrix representation
        for (int i = 0; i <= CHAPTERS; i++) {
            for (int j = 0; j <= CHAPTERS; j++) {
                if (j <= i) {
                    DAG[i][j] = NOLINK;
                }
                else {
                    sum = Math.Abs(avg_pages
                                   - (S[j] - S[i]));
                    DAG[i][j] = sum;
                }
            }
        }
        // 2) ............FIND OPTIMAL PATH................
        assignChapters(0, path, 0, 0, DAYS);
        // 3) ..PRINT OPTIMAL READ LIST USING OPTIMAL
        // PATH....
        Console.WriteLine("Optimal Chapter Assignment :");
        int ch = 1;
        for (int i = 0; i < DAYS; i++) {
            Console.Write("Day" + (i + 1) + ": ");
            int sumPages = 0;
            while (ch <= CHAPTERS
                   && (sumPages + pages[ch - 1])
                          <= avg_pages * (i + 1)) {
                sumPages += pages[ch - 1];
                Console.Write(ch + " ");
                ch++;
            }
            Console.WriteLine();
        }
    }
    public static void Main(){
        int[] pages = { 7, 5, 6, 12 };
      // Get read list for given days
        minAssignment(pages);
    }
}


Javascript




// DFS based recursive function to store the optimal path
// in path[] of size path_len. The variable Sum stores
// Sum of all edges on current path. k is number of days
// spent so far.
function assignChapters(u, path, path_len, Sum, k) {
  // Ignore the assignment which requires
  // more than required days
  if (k < 0) return;
 
  // Current assignment of chapters to days
  path[path_len] = u;
  path_len++;
 
  // Update the optimal assignment if necessary
  if (k == 0 && u == CHAPTERS) {
    if (Sum < Min) {
      updateAssignment(path, path_len);
      Min = Sum;
    }
  }
 
  // DFS - Depth First Search for sink
  for (let v = u + 1; v <= CHAPTERS; v++) {
    Sum += DAG[u][v];
    assignChapters(v, path, path_len, Sum, k - 1);
    Sum -= DAG[u][v];
  }
}
 
// This function finds and prints optimal read list.
// It first creates a graph, then calls assignChapters().
function MinAssignment(pages) {
  // Partial Sum array construction S[i] = total pages
  // till ith chapter
  let avg_pages = 0;
  let Sum = 0;
  let S = Array(CHAPTERS + 1).fill(null);
  let path = Array(DAYS + 1).fill(null);
  S[0] = 0;
 
  for (let i = 0; i < CHAPTERS; i++) {
    Sum += pages[i];
    S[i + 1] = Sum;
  }
 
  // Average pages to be read in a day
  avg_pages = Math.round(Sum / DAYS);
 
  // DAG construction vertices being chapter name &
  // Edge weight being |avg_pages - pages in a chapter|
  // Adjacency matrix representation
  for (let i = 0; i <= CHAPTERS; i++) {
    DAG[i] = Array(CHAPTERS + 1).fill(null);
    for (let j = 0; j <= CHAPTERS; j++) {
      if (j <= i) {
        DAG[i][j] = NOLINK;
      } else {
        Sum = Math.abs(avg_pages - (S[j] - S[i]));
        DAG[i][j] = Sum;
      }
    }
  }
 
  // Find optimal path
  assignChapters(0, path, 0, 0, DAYS);
 
  // Print optimal read list using optimal path
  console.log("Optimal Chapter Assignment :"+"<br>");
  let ch = null;
  for (let i = 0; i < DAYS; i++) {
    ch = optimal_path[i];
    console.log(`Day ${i + 1}: ${ch} `);
    ch += 1;
    while ((i < DAYS - 1 && ch < optimal_path[i + 1]) || (i == DAYS - 1 && ch <= CHAPTERS)) {
      console.log(`${ch} `);
      ch += 1;
    }
   console.log("<br>");
  }
}
 
// This function updates optimal_path[]
function updateAssignment(path, path_len) {
  for (let i = 0; i < path_len; i++) {
    optimal_path[i] = path[i] + 1;
  }
}
 
// Driver Code
const CHAPTERS = 4;
const DAYS = 3;
const NOLINK = -1;
 
let optimal_path = Array(DAYS + 1).fill(null);
 
// Graph - Node chapter+1 is the sink
// described in the above graph
let DAG = [];
for (let i = 0; i <= CHAPTERS; i++) {
DAG[i] = Array(CHAPTERS + 1).fill(null);
}
 
let Min = 999999999999;
const pages = [7, 5, 6, 12];
MinAssignment(pages)
 
// This code is contributed by lokeshpotta20.


Output

Optimal Chapter Assignment :
Day1: 1 
Day2: 2 3 
Day3: 4 


Last Updated : 31 Mar, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads