P2370 yyy2015c01 的 U 盘
P2370 yyy2015c01 的 U 盘
基础思路
看到题目要求最小需要的最大接口。自然认为既然答案要求接口,那状态方程的值就是接口。
一开始状态方程F[i][j]
,\(i\)为前\(i\)个接口,\(j\)为当前体积。而F[i][j]
则为当前最小的最大接口值
状态转移方程F[i][j] = min(F[i][j], (F[i - 1][j - v[i]] == INF) ? v[i] : max(F[i - 1][j - w[i]], v[i]))
然而这根本没法达到最优价值,更别提达到价值要求。
然后我又更改方程,\(j\)表示当前价值。但是这更错,首先当前价值未必最优,而且这下体积也没法保证小于U盘总体积。
冥思苦想,似乎要创造新背包算法了。
改进思路
然而,我的逻辑上有一个问题,我认为DP题一定要把答案设置为状态转移方程的值。
实则未必,这道题剔除最大接口后,就是一个01背包的板子。
当跑完01背包后,如何判断最大接口是问题。
这里我觉得两种做法很好。
结合排序
直接先按照体积排序,这样找到答案(能装下的最大价值比要求价值大的状态)直接输出就是装入物品的最大体积,即要求的接口。
这个方法代码复杂度不高,很容易实现。但是思考复杂度还是有,比较难想到。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x7fffffff
using namespace std;
struct yyy
{
int v, w;
bool operator< (const yyy &rhs)
const
{
return v < rhs.v;
}
};
int n, p, s;
yyy a[1010];
int F[1010];
int main()
{
cin >> n >> p >> s;
for (int i = 1; i <= n; i++)
{
cin >> a[i].v >> a[i].w;
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
{
for (int j = s; j >= a[i].v; j--)
{
F[j] = max(F[j], F[j - a[i].v] + a[i].w);
if (F[j] >= p)
{
printf("%d", a[i].v);
return 0;
}
}
}
printf("No Solution!");
return 0;
}
二分答案
首先看到最小的最大接口,很容易想出来可以二分答案。
思路也很明了,二分接口值,DP即可。
代码和思维都比较容易实现,就是时间复杂度可能略高。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x7fffffff
using namespace std;
struct yyy
{
int v, w;
bool operator< (const yyy &rhs)
const
{
return v < rhs.v;
}
};
int n, p, s;
yyy a[1010];
int F[1010];
bool DP(int x)
{
memset(F, 0, sizeof(F));
for (int i = 1; i <= n; i++)
{
if (a[i].v > x)
continue;
for (int j = s; j >= a[i].v; j--)
{
F[j] = max(F[j], F[j - a[i].v] + a[i].w);
if (F[j] >= p)
{
return true;
}
}
}
return false;
}
int main()
{
cin >> n >> p >> s;
for (int i = 1; i <= n; i++)
{
cin >> a[i].v >> a[i].w;
}
int l = 1, r = s, ans = 0;
while (l <= r)
{
int mid = l + ((r - l) >> 1);
if (DP(mid))
{
ans = mid;
r = mid - 1;
}
else
{
l = mid + 1;
}
}
if (!ans)
printf("No Solution!");
else
printf("%d", ans);
return 0;
}
总结
这题主要是战略出现了问题,以后看dp问题的时候不能只有dp,应该想想结合其他算法。状态方程也未必要和答案直接挂钩。