Sushi

1148

ข้อนี้มี Sushi ยาว nn (n1000000)(n\leq 1000000) ที่สามารถตัดได้ที่ mm (m20000)(m\leq 20000) ตำแหน่ง คือที่ระยะ R1,R2,,RmR_1,R_2,\dots,R_m จากด้านซ้าย โดยจะแบ่งให้เพื่อน kk (k20000)(k\leq 20000) คน คนละหนึ่งชิ้นหลังตัด โดยคนที่ ii จะได้รับส่วนที่ ii จากด้านซ้ายเรียงกันหลังการตัด

เพื่อนคนที่ ii มีค่าความชอบ PiP_i (Pi1000)(P_i \leq 1000) และจะได้รับความสุขเป็น PiP_i คูณความยาวของชิ้นที่ได้รับ (ต้องได้ชิ้นที่มีความยาวมากกว่า 00)

โจทย์ถามว่าจะได้ความสุขรวมมากที่สุดเท่าไหร่

Dynamic Programming

เพื่อความสะดวกกำหนดให้ R0=0,Rm+1=nR_0=0, R_{m+1}=n

ข้อนนี้สามารถมองเป็นโจทย์ Dynamic Programming โดยให้ DP[i][j]DP[i][j] แทนค่ารวมมากสุดที่เป็นไปได้หากแบ่ง Sushi แล้วถึง RiR_i โดยคนที่ได้รับชิ้น ii คือเพื่อนที่ jj

เราจะเริ่มจาก DP[0][0]=0DP[0][0] = 0 เพราะยังไม่มีความสุขและยังไม่ได้ให้ Sushi ชิ้นใดกับเพื่อนคนไหน และ DP[0][j]=DP[0][j] = -\infty เพราะไม่สามารถจบที่เพื่อนคนที่ jj โดยยังไม่มีใครได้สักชิ้น

จากนั้นสามารถพิจารณากรณี i1i\geq 1

สังเกตว่า DP[i][1]DP[i][1] จะเท่ากับ Ri×P1R_i \times P_1 เพราะเพื่อนถ้าคนแรกได้ถึง RiR_i จะต้องได้ทั้งหมดตั้งแต่ 00 ถึง RiR_i

สำหรับ j>1j>1 จะสังเกตว่า DP[i][j]=max(DP[i1][j1],DP[i1][j])+(RiRi1)PjDP[i][j] = \max(DP[i-1][j-1], DP[i-1][j]) + (R_i - R_{i-1}) P_j เพราะถ้าจะจบการแบ่งถึง RiR_i โดยให้เพื่อนคนที่ jj ชิ้นก่อนหน้าถึง Ri1R_{i-1} จะต้องถูกแบ่งให้เพื่อนคนที่ jj หรือ j1j-1 เท่านั้น ซึ่งจะเป็น max(DP[i1][j1],DP[i1][j])\max(DP[i-1][j-1], DP[i-1][j]) และการให้ช่วง [Ri1,Ri][R_{i-1},R_i] กับคนที่ jj จะได้ความสุข (RiRi1)Pj(R_i - R_{i-1}) P_j

เห็นได้ว่าการคำนวณแต่ละช่องของ DPDP จะใช้เวลา O(1)\mathcal{O}(1) มี mkmk ช่องทั้งหมดจึงเป็น O(mk)\mathcal{O}(mk)

อย่างไรก็ตามเนื่องจากข้อนี้ให้ Memory เพียง 16 Mb จะไม่สามารถประกาศ DPDP ให้มี mkmk ช่องโดยตรงจึงต้องหาวิธีลดการใช้ Memory

สังเกตว่าในการคำนวณ DPi[j]=DP[i][j]DP_i[j] = DP[i][j] สำหรับ ii ใดๆ เราจะต้องใช้เพียง Array DPi1DP_{i-1} เพราะในสูตรที่ใช้จะใช้เพียง DPi1DP_{i-1} โดยไม่ต้องใช้ DP1,DP2,,DPi2DP_{1}, DP_2, \dots, DP_{i-2} ดังนั้น ณ เวลาใดๆ จะต้องเก็บอย่างมาก 2k2k ค่า ทำให้ลดการใช้ Memory เป็น O(k)\mathcal{O}(k) โดยไม่เพิ่ม Time Complexity

ตัวอย่างโค้ด

#include <iostream>

using namespace std;

int R[20010];
int P[20010];

int DP[2][20010];
int main() {
  int n, m, k;
  cin >> n >> m >> k;

  for (int i = 1; i <= m; i++)
    cin >> R[i];
  R[m + 1] = n;

  for (int i = 1; i <= k; i++)
    cin >> P[i];

  DP[0][0] = 0;
  for (int i = 1; i <= m; i++)
    DP[0][i] = -1000000000;

  for (int i = 1; i <= m + 1; i++) {
    DP[i % 2][1] = R[i] * P[1];
    for (int j = 2; j <= k; j++)
      DP[i % 2][j] = max(DP[(i + 1) % 2][j], DP[(i + 1) % 2][j - 1]) + (R[i] - R[i - 1]) * P[j];
  }

  cout << DP[(m + 1) % 2][k];
}

ในโค้ดนี้จะใช้ DP[0] กับ DP[1] สลับกันโดย DP[i%2] จะใช้แทน DPiDP_i ในแต่ละขั้น และ DP[(i+1)%2] จะแทน DPi1DP_{i-1}