renshale
2020-04-22 08:47:42
由于作者是一个初二蒟蒻,有一些地方可能存在问题,请多指教。喷轻点(ZXJ不要打我啦)
其实,
由于其确定性时间复杂度算法,时间复杂度呈指数级增长,所以该题是一个其实不是这样证的,这个只是想说明ZKP没有多项式算法而已,大雾)
(评论区DL:《算法导论》上有证明)
思路分析:这道题目由于
预处理:我们将数据分成规模相同的两半,两边分别进行搜索,每一种
将两部分数据以
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 41
unsigned long long INF = (1ULL << 63) - 1;
int n;
unsigned long long w[N], v[N];
unsigned long long m, ans;
pair< unsigned long long, unsigned long long > a[1 << (N / 2)], b[1 << (N / 2)];
void solve()
{
int half_n = n >> 1;
for(int i = 0; i < 1 << half_n; i ++)
{
unsigned long long sw = 0, sv = 0;
for(int j = 0; j < half_n; j ++)
{
if(i >> j & 1)
{
sw += w[j];
sv += v[j];
}
}
a[i] = make_pair(sw, sv);
}
sort(a, a + (1 << half_n));
int la = 1;
for(int i = 1; i < 1 << half_n; i ++)
if(a[la - 1].second < a[i].second)
a[la ++]=a[i];
for(int i = 0; i < 1 << (n - half_n); i ++)
{
unsigned long long sw = 0, sv = 0;
for(int j = 0; j < n - half_n; j ++)
{
if(i >> j & 1)
{
sw += w[half_n + j];
sv += v[half_n + j];
}
}
b[i] = make_pair(sw, sv);
}
sort(b, b + (1 << half_n));
int lb = 1;
for(int i = 1; i < 1 << half_n; i ++)
if(b[lb - 1].second < b[i].second)
b[lb ++] = b[i];
int mc = 0;
for (int i = la - 1, j = 0; ~i; i --)
{
for (; j < lb && a[i].first + b[j].first <= m; j ++)
if (b[j].second > mc) mc = b[j].second;
if (a[i].first <= m && mc + a[i].second > ans) ans = mc + a[i].second;
}
printf("%lld\n", ans);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%llu %d", &m, &n);
for(int i = 0; i < n; i ++) scanf("%llu %llu", &w[i], &v[i]);
solve();
return 0;
}
所以应该叫“尺取法”?
将一部分数据以
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 41
unsigned long long INF = (1ULL << 63) - 1;
int n;
unsigned long long w[N], v[N];
unsigned long long m, ans;
pair< unsigned long long, unsigned long long > a[1 << (N / 2)];
void solve()
{
int half_n = n >> 1;
for(int i = 0; i < 1 << half_n; i ++)
{
unsigned long long sw = 0, sv = 0;
for(int j = 0; j < half_n; j ++)
{
if(i >> j & 1)
{
sw += w[j];
sv += v[j];
}
}
a[i] = make_pair(sw, sv);
}
sort(a, a + (1 << half_n));
int l = 1;
for(int i = 1; i < 1 << half_n; i ++)
if(a[l - 1].second < a[i].second)
a[l ++]=a[i];
for(int i = 0; i < 1 << (n - half_n); i ++)
{
unsigned long long sw = 0, sv = 0;
for(int j = 0; j < n - half_n; j ++)
{
if(i >> j & 1)
{
sw += w[half_n + j];
sv += v[half_n + j];
}
}
if(sw <= m)
{
unsigned long long tv = (lower_bound(a, a + l,make_pair(m - sw, INF)) - 1) -> second;
ans = max(ans , sv + tv);
}
}
printf("%lld\n", ans);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%llu %d", &m, &n);
for(int i = 0; i < n; i ++) scanf("%llu %llu", &w[i], &v[i]);
solve();
return 0;
}
两个方法的效率都差不多,时间复杂度:快速的
推荐使用双指针法,二分法常数较大
数据范围:
思路分析:由于
注意:下文中
此法较好理解,根据二进制表示法,
时间复杂度:
此法较难理解,主要思想在于将
时间复杂度:
(noip不考)(其实作者也不大会)
不会的右转
数据范围:
DP,时间复杂度:所以说标题是伪吗……)
由于以上算法涉及多重背包,容易找到资料,故不在此实现
相信大家最开始做
数据范围:
来自
思路分析:这道题看上去,前文所提到的算法都无法很好的解决,然而生活中遇到的常常是无法用确定性算法解决的问题,所以这题我们采用搜索+剪枝
预处理:按照玄学)(因为这样有获得更大答案的倾向啊,大雾)
状态:
来自
思路分析:也是搜索+剪枝呢,只不过更强一点
预处理:按照
即为上面的剪枝1和剪枝2
在dfs时维护
当
意义:当
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
struct node
{
int w, v;
double wv;
} a[N];
int n, m, t, ans, sum;
int sumw[N], sumv[N];
inline bool cmp (node x, node y)
{
return x.w < y.w || (x.w == y.w && x.v > y.v);
}
void dfs(int x, int y, int z, int p)
{
if (x > n)
{
if (z > ans) ans = z;
return ;
}
if (y + sumw[x] <= m)
{
if (z + sumv[x] > ans)
ans = z + sumv[x];
return ;
}
if (y + a[x].w <= m && a[x].v > p)
dfs(x + 1, y + a[x].w, z + a[x].v, p);
if (z + sumv[x + 1] > ans)
dfs(x + 1, y, z, a[x].v > p ? a[x].v : p);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &a[i].w, &a[i].v);
a[i].wv = (double) a[i].v / a[i].w;
}
sort(a + 1, a + n + 1, cmp);
/* for (int k = 1; k <= n; k ++)
{
sum = 0, t = m;
for (int i = k; i <= n; i ++)
if (t >= a[i].w)
{
sum += a[i].v;
t -= a[i].w;
}
if (sum > ans) ans = sum;
} */
for (int i = n; i > 0; i --)
{
sumw[i] = sumw[i + 1] + a[i].w;
sumv[i] = sumv[i + 1] + a[i].v;
}
dfs(1, 0, 0, 0);
printf("%d", ans);
return 0;
}
由于某种未知的原因,例2所用的解法比例1要优,所以推荐使用例2的方法
在下文中,我们把一个物品的
随机次数变量为
平均搜索常数为
前言:大家在01背包的问题中,很容易想到贪心算法,因为正常人都知道,价值比大的物品放进背包里更优,这在部分背包(可以将物品的一部分放入背包中的背包问题)里是正确的,但是在01背包中,我们还要考虑背包的剩余空间是否能装下整的一个物品,所以这个方法可以构造出许多反例,但是由于其提供了一个别样的思路,所以对下文的许多算法得实现都造成了启发。
方法:将
由于从头至尾的贪心不一定最优,我们可以考虑换一个起点进行贪心,获得更优解,通过实际测试得出(因为论文里没讲),更换
以下给出普适版的
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
struct node
{
int w, v;
double wv;
} a[N];
int n, m, t, ans, sum;
inline bool cmp (node x, node y)
{
return x.wv > y.wv || (x.wv == y.wv && x.v > y.v);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &a[i].w, &a[i].v);
a[i].wv = (double) a[i].v / a[i].w;
}
sort(a + 1, a + n + 1, cmp);
for (int k = 1; k <= n; k ++)
{
sum = 0, t = m;
for (int i = k; i <= n; i ++)
if (t >= a[i].w)
{
sum += a[i].v;
t -= a[i].w;
}
if (sum > ans)
ans = sum;
}
printf("%d", ans);
return 0;
}
注意:从
前置条件:在上文的贪心的基础上,我们得知一个事实,虽然价值比大的物品不一定优先放入背包,但是它放入背包的概率比其它物品的概率更大。
方法:所以我们可以构造一个概率函数
时间复杂度:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
#define Times 100000
struct node
{
int w, v;
double wv;
} a[N];
int n, m, t, ans, sum;
inline bool cmp (node x, node y)
{
return x.wv > y.wv || (x.wv == y.wv && x.v > y.v);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
srand(19260817);
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &a[i].w, &a[i].v);
a[i].wv = (double) a[i].v / a[i].w;
}
sort(a + 1, a + n + 1, cmp);
for (int T = 1; T <= Times; T ++)
{
sum = 0, t = m;
for (int i = 1; i <= n; i ++)
if (rand() % (n - i + 2) && t >= a[i].w)
{
sum += a[i].v;
t -= a[i].w;
}
if (sum > ans)
ans = sum;
}
printf("%d", ans);
return 0;
}
东方玄学种子
前置条件:同样,在上文的贪心的基础上,我们得知一个事实,虽然排序后的序列不是最优序列,但它是接近最优序列的。
方法:所以我们可以把它的优先级进行微调,如何微调呢?我们引进一个取值范围为
时间复杂度:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
#define Times 10000
struct node
{
int w, v;
double wv, wvl;
} a[N];
int n, m, t, ans, sum;
inline bool cmp (node x, node y)
{
return x.wv > y.wv || (x.wv == y.wv && x.v > y.v);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
srand(19260817);
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &a[i].w, &a[i].v);
a[i].wvl = a[i].wv = (double) a[i].v / a[i].w;
}
sort(a + 1, a + n + 1, cmp);
for (int T = 1; T <= Times; T ++)
{
sum = 0, t = m;
for (int i = 1; i <= n; i ++)
if (rand() % (n - i + 2) && t >= a[i].w)
{
sum += a[i].v;
t -= a[i].w;
}
if (sum > ans)
ans = sum;
for (int i = 1; i <= n; i ++)
{
double eta = 0.5 + 0.5 * rand() / RAND_MAX;
a[i].wv = a[i].wvl * eta;
}
sort(a + 1, a + n + 1, cmp);
}
printf("%d", ans);
return 0;
}
东方玄学种子
思路分析:由于搜索不能快速的解决问题,我们考虑用人的思维去帮助它,也就是借用
状态:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
struct node
{
int w, v;
double wv;
} a[N];
int n, m, t, ans;
int sumw[N], sumv[N];
inline bool cmp (node x, node y)
{
return x.wv > y.wv || (x.wv == y.wv && x.v > y.v);
}
inline int func(int sx, int sw)
{
int res = 0;
for (int i = sx + 1; i <= n; i ++)
{
if (sw + a[i].w <= m)
{
sw += a[i].w;
res += a[i].v;
}
else
return (int) (res + (m - sw) * a[i].wv);
}
return res;
}
void dfs(int x, int y, int z)
{
if (z > ans) ans = z;
if (x > n) return ;
if (func(x, y) + z > ans) dfs(x + 1, y, z);
if (y + a[x].w <= m) dfs(x + 1, y + a[x].w, z + a[x].v);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%d %d", &m, &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d %d", &a[i].w, &a[i].v);
a[i].wv = (double) a[i].v / a[i].w;
}
sort(a + 1, a + n + 1, cmp);
dfs(1, 0, 0);
printf("%d", ans);
return 0;
}
小优化:如果估价值可以由上一个状态转移而来,时间复杂度应该可以降到作者也没试过,大家可以试一试)
这个东西好像跟估价函数法差不多,但是改用
时间复杂度:
上面的两个方法使用贪心初始流可以降低
这是一个神奇的东西,可以成批确定一定在背包最优解中的物品和成批排除一定不在背包最优解中的物品。
具体证明右转,这里讲过程(以及感性理解)
(由于这是论文,要登录我也没办法)
一个背包问题的数据集表示为
背包大小表示为
上界表示为
下界表示为
由于
任何一个可行解都可以作为该问题的一个
这里有两种:
预处理:按价值比排序
枚举一个
排完序以后,我们由
意义:重量大,价值又小的状态不用加入队列
(此处可以用一个新队列来实现,确保其在队列中有序)
期望时间复杂度:
当原来的
期望时间复杂度:
这里可能有人会问,为什么期望时间复杂度是线性的呢?明明
但是你要看看,数据是随机的呢,加上了剪枝以后,我们将
(管理员大大对此提出了不同的看法,我的想法就是,将
关于在随机数据下,
巨佬的代码(附题面)
(完)
致谢:@
引用来自百度文库、中国知网及诸多链接大部分来自洛谷日报)
非常欢迎提出新方法或指出错误