题解:P14259 兄妹(siblings)
这篇题解是对第二篇题解的补充说明。这里推一下我的博客。
分析
这里有一个贪心策略:如果有一个人要进入某一行,他就必须把这一行的所有书本全部放进去。证明:这里考虑反证法。如果肯定有一个人需要到达最远的地方放书,那么他一定路过其他的书,也就可以帮另一个人放了,所以如果这个人进去了,另一个人就一定不需要进入这一行。所以我们就可以发现只需要记录每一行最远的书在哪里就可以了,因为
01背包
会01背包的童鞋们可以跳过这一段。
01背包解决的问题就是:给你
暴力是很简单的,可以直接用01DFS选择,这时候我们发现,当
我们又发现,如果我们把选
我们还知道,在暴力中有一种东西叫做记忆化搜索,就是把之前的答案存起来可以让他不用重复计算,我们开数组就可以把之前的存起来,然后方便后面使用,这就是动态规划的子问题重叠性。
我们发现01背包刚好满足这三个性质,所以我们可以使用动态规划。因为影响背包价值的只有抉择了哪些物品和这些物品的重量,所以我们定义
void DP() {
for (int i=1;i<=n;i++) {
for (int j=W;j>=w[i];j--) {
dp[i][j]=max({dp[i][j],dp[i-1][j],dp[i-1][j-w[i]]+v[i]});
}
}
}
又因为
void DP() {
for (int i=1;i<=n;i++) {
for (int j=W;j>=w[i];j--) {
dp[i%2][j]=max({dp[i%2][j],dp[(i-1)%2][j],dp[(i-1)%2][j-w[i]]+v[i]});
}
}
}
回到这道题,我们可以把问题从最大的价值转换成可以拼成什么价值,类似求给出
void DP() {
for (int i=1;i<=n;i++) {
for (int j=W;j>=w[i];j--) {
dp[i%2][j]=dp[(i-1)%2][j];
dp[i%2][j]|=dp[(i-1)%2][j-w[i]]+v[i]});
}
}
}
只不过这里因为是求两者的最大值最小,就是看每种情况是否可行,如果可行,当前的重量和剩余的重量取最大值就是当前的答案,每次都取最小值就可以了。
思路总结
首先我们可以先求出每一行的最大的
Q:为什么要乘
A:看下面这个例子:
| |
| | | |
-------------
0 1 2 3 4 5 6
假设一个人从第
核心代码如下:
for (int i=1;i<=n;i++) {
if (!h[i]) continue;
for (int j=sum;j>=0;j--) {
dp[i%2][j]|=dp[(i-1)%2][j];
dp[i%2][j+h[i]]|=dp[i%2][j];
}
sum+=h[i];
for (int j=0;j<=sum;j++) {
if (dp[i%2][j]) {
ans=min(ans,max(i+j,n+sumc-j));
}
}
}
代码
int dp[2][maxv*maxv];
void solve() {
memset(dp,0,sizeof(dp)); // 记得初始化
int t; cin >> t;
vector<int> h;
for (int i=1;i<=t;i++) {
int r,c; cin >> r >> c;
if (h.size()<r+1) h.resize(r+1);
h[r]=max(h[r],c);
}
int n=h.size()-1;
int sum=0;
int sumc=0;
int ans=1e9;
dp[0][0]=1;
for (int i=1;i<=n;i++) sumc+=h[i];
for (int i=1;i<=n;i++) {
if (!h[i]) continue;
for (int j=sum;j>=0;j--) {
dp[i%2][j]|=dp[(i-1)%2][j];
dp[i%2][j+h[i]]|=dp[i%2][j];
}
sum+=h[i];
for (int j=0;j<=sum;j++) {
if (dp[i%2][j]) {
ans=min(ans,max(i+j,n+sumc-j));
}
}
}
cout << 2*ans << endl;
}