洛谷 P6046 [CTSC2000]快乐的蜜月

\(\quad\) 先讲解一下如何处理这道题的毒瘤输入。\(m\)\(d\) 之间的“/”和“ TO ”都可以用 getchar() 强行吃掉,日期的转换可以用公式 \(s_{m-1} + d\) 表示,其中 \(s\) 是每月日数的前缀和。

int s[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

inline int getid(int month, int day){
    return s[month - 1] + day;
}

int main(){
    ...
    if(y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
        s[2] = 29;
    for(int i = 1; i <= 12; ++i)
        s[i] += s[i - 1];
    ...
}

\(\quad\) 可以用链式前向星存储预约。对于每一份从 \(i\) 日到 \(j\) 日的预约,建立一条从 \(j\)\(i\) 的边。
\(\quad\) 如果是 \(k = 1\) 的特殊情况,很容易想到 \(O(r)\) 的动态规划做法:
\(\qquad\bullet\)\(dp_i\) 为到 \(i\) 日时的最大收入。
\(\qquad\bullet\) \(dp_i = max \left\{ dp_{i - 1}, dp_j + p \cdot (i - j) \right\}\),其中 \(j\) 满足存在自 \(j\) 日到 \(i\) 日的预定。
\(\quad\) 对于 \(k \neq 1\) 的情况,可以二维 dp。即:
\(\qquad\bullet\) \(dp_i\) 是一个大小为 \(k\) 的有序集合,从 \(dp_{i,1}\)\(dp_{i,k}\) 分别表示到 \(i\) 日的前 \(k\) 大收入。
\(\qquad\bullet\) \(dp_i = dp_{i - 1} \cup dp ^ \prime _ j\),其中 \(j\) 满足存在自 \(j\) 日到 \(i\) 日的预定,对于\(dp ^ \prime _ j\) 中的每一个元素 \(dp ^ \prime_{j, a}\),有 \(dp ^ \prime_{j, a} = dp_{j, a} + p \cdot (i - j)\)
\(\quad\) 显然,所谓的“有序集合”可以用数组模拟,两个有序数列的合并操作就是二路归并。多余的元素(即排名超过 \(k\) 的元素)不予维护。

void merge(int *dest, int *src1, int *src2){
    for(int i = 0; i < k; ++i)
        *(dest++) = *src1 > *src2 ? *(src1++) : *(src2++);
}

\(\quad\) 以上的代码是二路归并的指针写法。这一题要求非重复的第 \(k\) 大,注意去重。
\(\quad\) 收入为 \(0\) 也是一种可能,不可以在 \(dp_{s_{12}, k} = 0\) 时直接输出 \(-1\)。正确做法如下:

if(dp[s[12]][k - 1] == 0)
    printf("-1\n");
else
    printf("%d\n", dp[s[12]][k]);

\(\quad\) 时间复杂度 \(O(k \cdot r)\)

猜你喜欢

转载自www.cnblogs.com/natsuka/p/12296178.html