Copying Books UVA - 714
记录这题呢主要是之前在学校集训的时候疯狂WA没有做出来,实在找不出bug放弃了。碰巧刷紫书到了这题然后再做一遍,嗯,叕叕又又WA了。。然后就去udebug找数据,最后终于A掉了。
这题的bug确实很隐蔽,没有数据对我来说很难想到。其实如果一开始就全局剔除直观上的不合法范围就可以有效避免这些问题,更深层次的问题我也想不出来了,也许只是我太菜了。
#include <iostream> #include <cstdio> #include <cstring> #include <cassert> //#define fre //#define DEBUG #define ll long long using namespace std; ll p[505]; int m, k, sep[505]; bool judge(ll w) { ll sum = 0; int cnt = 0; for (int i = 0; i < m; ++i) { if (sum + p[i] > w) sum = p[i], ++cnt; /*关键的bug在这一步,从逻辑上看似乎没有问题,但是在sum + p[i] > w时把sum改成p[i]相当于默认了这一步p[i]是合法的,而事实上存在p[i] > w的情况事实上仔细分析这段代码会发现每次判断出sum超过范围时会以上一本书作为分隔,也就是第i-1本书是上一个人分配的最后一本书,但这应当满足此时sum应当是合法的,即不超过w,但通过上一句分析我们知道存在p[i]本身大于w的情况。 这里的核心在于如果我们假设到这一步为止sum都是合法的,那么实际上这里它变化了两次:一次是变为sum+p[i],另一次是变为p[i],而两次变化后都需要检验,这里只判断了第一次。 那如何要想到我们要始终维护sum < w呢?其实这里就要考虑清楚这个循环的意义:找出当前的w下,所能分配的最少的人数。那么当p[i] > w时,显然直接就不能分配了,cnt根本无法计算,或是说没有一个合乎定义的方式来计算。因此最好的方法就是在下面开始的初始化搜索范围时选取一个尽量合理的范围来规避这个问题。(不过大多数情况下都需要bug来帮助(狗头))*/ else sum += p[i]; } if (sum > w) return false; if (cnt + 1 > k) return false; else return true; } ll bisearch(ll l, ll r) { ll mid; while (l < r) { mid = l + (r - l) / 2; if (judge(mid)) r = mid; else l = mid + 1; } return l; } int main() { #ifdef fre freopen("in.in", "r", stdin); freopen("out.txt", "w", stdout); #endif int t, cn; ll sum, ans, cs, mx; scanf("%d", &t); while (t--) { scanf("%d%d", &m, &k); memset(p, 0, sizeof(p)); memset(sep, 0, sizeof(sep)); sum = 0, mx = 0; for (int i = 0; i < m; ++i) scanf("%lld", &p[i]), sum += p[i], mx = max(p[i], mx); ans = bisearch(mx, sum); // 注意起点应当从所有页数的最大值开始收,可以见上面的分析 #ifdef DEBUG cout << ans << endl; #endif cs = 0, cn = 0; for (int i = m - 1; i >= 1; --i) { if (cs + p[i] > ans) sep[i] = 1, cs = p[i], cn++; else cs += p[i]; if (i + cn + 1 == k) { for (int j = 0; j < i; ++j) sep[j] = 1; break; } } printf("%lld", p[0]); if (sep[0]) printf(" /"); for (int i = 1; i < m; ++i) { printf(" %lld", p[i]); if(sep[i]) printf(" /"); } putchar('\n'); } }