暑假N天乐 —— 完全背包及变形
好吧先承认一下错误...昨天放假然后牛客补题就咕咕咕了。
云顶之弈真好玩 六刺客赛高
掐指一算离牛客暑假多校开始就剩两天了...哦豁完蛋。
目前来说明天的计划是多重背包+分组背包,也算把背包先画上句号【树形背包打算后面和树形DP一起搞】。然后明天应该会把这几天背包的总结贴上,今天就emmm只上题解好了。
[UVA-674 Coin Change] 完全背包裸题
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=615
给定一个数要拆成由【50、25、10、5、1】组成,每种都无限,问有几种拆法。
就大概是个纯板子,就放这了。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int val[5];
ll dp[10005];
int main() {
val[0] = 1;
val[1] = 5;
val[2] = 10;
val[3] = 25;
val[4] = 50;
int n;
while(~scanf("%d", &n)) {
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 0; i < 5; i++) {
for(int j = val[i]; j <= n; j++) {
dp[j] += dp[j-val[i]];
}
}
printf("%lld\n", dp[n]);
}
return 0;
}
[POJ- Dollars] 完全背包裸题
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=83
和上题基本意思一样,但因为小数的存在,所以需要做精度处理,只能说多了个坑(然后我就跳进去了)。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int val[15];
ll dp[50005];
int main() {
val[0] = 5;
val[1] = 10;
val[2] = 20;
val[3] = 50;
val[4] = 100;
val[5] = 200;
val[6] = 500;
val[7] = 1000;
val[8] = 2000;
val[9] = 5000;
val[10] = 10000;
int n;
int a, b;
while(scanf("%d.%d",&a,&b)&&(a+b)) {
n = 100*a + b;
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 0; i < 11; i++) {
for(int j = val[i]; j <= n; j++) {
dp[j] += dp[j-val[i]];
}
}
printf("%6.2f%17lld\n", n*1.0/100, dp[n]);
}
return 0;
}
[POJ-3181 Dollar Dayz] 完全背包常规(整数划分高精度处理)
http://poj.org/problem?id=3181
上一题的更进一步,把精度问题从小数点换成了爆longlong。
然后这题其实是正统的整数划分问题。以下详解转自网络(侵删):
整数划分是把一个正整数 N 拆分成一组数相加并且等于 N 的问题。
假设\(F(N,M)\)表示整数 N 的划分个数,其中 M 表示将 N 拆分后的序列中最大数。
考虑边界状态:
- M = 1 或者 N = 1 只有一个划分,即:\(F(1,1) = 1\)
- M = N:等于把M - 1 的划分数加 1 即:\(F(N,N) = F(N,N-1) + 1\)
- M > N:按理说,N 划分后的序列中最大数是不会超过 N 的,所以\(F(N,M ) = F(N,N)\)
- M < N:这个是最常见的,他应该是序列中最大数为 M-1 的划分和 N-M 的划分之和,所以\(F(N,M) = F(N, M-1) + F(N-M,M)\)
用动态规划来表示【\(dp[n][m]\)表示整数 n 的划分中,每个数不大于 m 的划分数】:$$dp[n][m]= dp[n][m-1]+ dp[n-m][m]$$
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e3+5;
ll a[maxn][maxn], b[maxn][maxn];
int main() {
int n, k;
ll MAX = 1;
for(int i = 0; i < 18; i++) {
MAX *= 10;
}
while(~scanf("%d%d", &n, &k)) {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
for(int i = 0; i <= k; i++) {
a[0][i] = 1;
}
for(int j = 1; j <= k; j++) {
for(int i = 1; i <= n; i++) {
if(i < j) {
a[i][j] = a[i][j-1];
b[i][j] = b[i][j-1];
}
else {
b[i][j] = b[i-j][j] + b[i][j-1] + (a[i-j][j]+a[i][j-1])/MAX;
a[i][j] = (a[i-j][j]+a[i][j-1]) % MAX;
}
}
}
if(b[n][k]) {
printf("%lld", b[n][k]);
}
printf("%lld\n", a[n][k]);
}
return 0;
}
[POJ-1787 Charlie's Change] 完全背包常规
http://poj.org/problem?id=1787
买咖啡,有4种硬币,要求付款金额最小的情况下花最多的硬币。
多重背包可做,但完全背包更快且好写,缺点...难想。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5+5;
int val[5];
int dp[maxn];
int vis[maxn];
int ans[maxn];
int temp[maxn];
int main() {
val[0] = 1;
val[1] = 5;
val[2] = 10;
val[3] = 25;
int n, a[5];
while(~scanf("%d%d%d%d%d", &n, &a[0], &a[1], &a[2], &a[3])) {
if(n == 0 && a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0) {
break;
}
memset(dp, 0, sizeof(dp));
memset(temp, 0, sizeof(temp));
memset(ans, 0, sizeof(ans));
dp[0] = 1;
for(int i = 0; i < 4; i++) {
memset(vis, 0, sizeof(vis));
for(int j = val[i]; j <= n; j++) {
if(dp[j-val[i]] && dp[j] < dp[j-val[i]] + 1 && vis[j-val[i]] < a[i]) {
dp[j] = dp[j-val[i]] + 1;
vis[j] = vis[j-val[i]] + 1;
temp[j] = j - val[i];
}
}
}
if(dp[n] == 0) {
printf("Charlie cannot buy coffee.\n");
}
else {
while(n) {
ans[n-temp[n]] ++;
n = temp[n];
}
printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n", ans[1], ans[5], ans[10], ans[25]);
}
}
return 0;
}
[POJ-3620 The Fewest Coins] 完全背包+多重背包
http://poj.org/problem?id=3260
完全背包和多重背包的综合题...给定 n 种硬币和每种的价值、数量。要买价值为 T 的物品,问经手的硬币最少多少枚。
找零用完全背包,付款用多重背包。先更新找零,最后循环找最小。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 2e5+5;
int dp1[maxn]; // 付款
int dp2[maxn]; // 找零
int val[105];
int c[105];
int main() {
int n, t;
while(~scanf("%d%d", &n, &t)) {
int temp = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &val[i]);
temp = max(temp, val[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
}
memset(dp1, inf, sizeof(dp1));
memset(dp2, inf, sizeof(dp2));
dp1[0] = dp2[0] = 0;
int MAX = temp*temp + t + 1;
for(int i = 1; i <= n; i++) {
int vv = val[i];
int cc = c[i];
// 找零(完全背包)
for(int j = vv; j <= MAX; j++) {
dp2[j] = min(dp2[j], dp2[j-vv]+1);
}
// 付款(多重背包)
if(vv*cc >= MAX) {
for(int j = vv; j <= MAX; j++) {
dp1[j] = min(dp1[j], dp1[j-vv]+1);
}
}
else {
int k = 1;
while(k < cc) {
for(int j = MAX; j >= k*vv; j--) {
dp1[j] = min(dp1[j], dp1[j-k*vv]+k);
}
cc -= k;
k = k*2;
}
for(int j = MAX; j >= cc*vv; j--) {
dp1[j] = min(dp1[j], dp1[j-cc*vv]+cc);
}
}
}
int ans = inf;
for(int i = t; i <= MAX; i++) {
ans = min(ans, dp1[i] + dp2[i-t]);
}
if(ans == inf) {
printf("-1\n");
}
else {
printf("%d\n", ans);
}
}
return 0;
}
[POJ-2063 Investment] 完全背包常规
http://poj.org/problem?id=2063
算利息...每年的钱可以利滚利问最多拿到多少钱。题目给了一个非常重要的信息...债券的价值是1000的倍数,循环的时候需要先 /1000 进行优化,不然...TLE。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5+5;
int dp[maxn];
int val[15], b[15];
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n, y;
scanf("%d%d", &n, &y);
int d;
scanf("%d", &d);
for(int i = 1; i <= d; i++) {
scanf("%d%d", &val[i], &b[i]);
}
for(int k = 1; k <= y; k++) {
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= d; i++) {
for(int j = val[i]/1000; j <= n/1000; j++) {
dp[j] = max(dp[j], dp[j-val[i]/1000]+b[i]);
}
}
n += dp[n/1000];
}
printf("%d\n", n);
}
return 0;
}
[ZOJ-3623 Battle Ships] 完全背包变形
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3623
建造战机打怪,每个战机有建造时间和攻击力(建好了就能一直打),问最快多久打死boss。
用时间作为 dp 数组的下标、记录当前总伤害。最后再循环找最快时间。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e3+5;
int t[35], d[35];
int dp[maxn];
int main() {
int n, l;
while(~scanf("%d%d", &n, &l)) {
for(int i = 1; i <= n; i++) {
scanf("%d%d", &t[i], &d[i]);
}
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++) {
for(int j = 0; j < 400; j++) {
dp[j+t[i]] = max(dp[j+t[i]], dp[j]+d[i]*j);
}
}
int ans = 0;
for(int i = 0; i < 400; i++) {
if(dp[i] >= l) {
ans = i;
break;
}
}
printf("%d\n", ans);
}
return 0;
}
[ZOJ-3524 Crazy Shopping] 完全背包+拓扑排序
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3524
就是一个无环单向图,给定起点和背包容量,从 u 到 v 获得的疲劳值是 两点间距离*当前背包容积。求取得最大值时的最小消耗。
需要注意拓扑排序出来的点不一定都能走,需要 vis 标记。然后转移的时候先转移前点过来的,再是之后的点(保证无后效性)。
v 写成了 u,wa 了一下午
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 6e2+5;
const int maxm = 6e4+5;
const int maxw = 2e3+5;
struct Edge {
int to, l, nxt;
}edge[maxm];
int c[maxn], val[maxn];
int in[maxn], head[maxn];
int topo[maxn], vis[maxn];
int dp[maxn][maxw]; // 走到 i 点,容量为 j 的 最大价值
int tp[maxn][maxw]; // 当最大价值为 dp[i][j] 时的 最小消耗
int n, m, w, x;
int MAXV, MINC;
int tot, cnt;
void init() {
memset(in, 0, sizeof(in));
memset(dp, 0, sizeof(dp));
memset(vis, 0, sizeof(vis));
memset(topo, 0, sizeof(topo));
memset(head, -1, sizeof(head));
memset(tp, inf, sizeof(tp));
MAXV = 0;
MINC = inf;
tot = 0;
cnt = 0;
}
void addedge(int u, int v, int l) {
edge[tot].to = v;
edge[tot].l = l;
edge[tot].nxt = head[u];
head[u] = tot++;
in[v] ++;
}
void Topo() {
queue<int> q;
for(int i = 1; i <= n; i++) {
if(in[i] == 0) {
q.push(i);
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
topo[cnt++] = u;
for(int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].to;
in[v] --;
if(in[v] == 0) {
q.push(v);
}
}
}
}
int main() {
while(~scanf("%d%d%d%d", &n, &m, &w, &x)) {
init();
for(int i = 1; i <= n; i++) {
scanf("%d%d", &c[i], &val[i]);
}
for(int i = 1; i <= m; i++) {
int u, v, l;
scanf("%d%d%d", &u, &v, &l);
addedge(u, v, l);
}
Topo();
vis[x] = 1;
dp[x][0] = tp[x][0] = 0;
for(int i = 0; i < cnt; i++) {
if(vis[topo[i]] == 0) {
continue;
}
int u = topo[i];
for(int j = c[u]; j <= w; j++) {
if(dp[u][j] < dp[u][j-c[u]]+val[u]) {
dp[u][j] = dp[u][j-c[u]] + val[u];
tp[u][j] = tp[u][j-c[u]];
}
else if(dp[u][j] == dp[u][j-c[u]]+val[u]) {
tp[u][j] = min(tp[u][j], tp[u][j-c[u]]);
}
}
for(int j = head[u]; ~j; j = edge[j].nxt) {
int v = edge[j].to;
vis[v] = 1;
for(int k = 0; k <= w; k++) {
if(dp[v][k] < dp[u][k]) {
dp[v][k] = dp[u][k];
tp[v][k] = tp[u][k] + k*edge[j].l;
}
else if(dp[v][k] == dp[u][k]) {
tp[v][k] = min(tp[v][k], tp[u][k]+k*edge[j].l);
}
}
}
for(int j = 0; j <= w; j++) {
if((dp[u][j] > MAXV) || (dp[u][j]==MAXV && tp[u][j] < MINC)) {
MAXV = dp[u][j];
MINC = tp[u][j];
}
}
}
printf("%d\n", MINC);
}
return 0;
}
[ZOJ-3662 Math Magic] 背包 + 状态压缩
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3662
把每个m的因子当作物品,要求我们随便选k个物品,使得这些物品的总和为n,Lcm为m。dp[i][j][k]表示选了i个物品,和为jlcm为k的方案数,具体做法是先预处理找出m的所有因子,这样不管怎么选,最后都有可能Lcm为m,免除很多无必要的计算,然后用这类背包的方法进行转移。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e3+5;
int a[maxn];
int lcm[maxn][maxn];
int dp[2][maxn][maxn]; // dp[i][j][k] : 第 i 个数,和为 j,lcm为 k
// i 表示滚动数组(100-1000-1000 开不下)
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x%y);
}
void init() {
for(int i = 1; i <= 1000; i++) {
for(int j = 1; j <= 1000; j++) {
lcm[i][j] = i / gcd(i, j) * j;
}
}
}
int main() {
init();
int n, m, k;
while(~scanf("%d%d%d", &n, &m, &k)) {
memset(dp, 0, sizeof(dp));
int cnt = 0;
for(int i = 1; i <= m; i++) {
if(m % i == 0) {
a[cnt++] = i;
}
}
dp[0][0][1] = 1;
int f;
for(int x = 1; x <= k; x++) {
f = x % 2;
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= m; j++) {
dp[f][i][j] = 0;
}
}
for(int i = x-1; i <= n; i++) {
for(int j = 0; j < cnt; j++) {
if(dp[f^1][i][a[j]] == 0) {
continue;
}
for(int l = 0; l < cnt; l++) {
int temp = lcm[a[j]][a[l]];
if(a[l] + i > n || m % temp != 0) {
continue;
}
dp[f][a[l]+i][temp] = (dp[f][a[l]+i][temp] + dp[f^1][i][a[j]]) % mod;
}
}
}
}
printf("%d\n", dp[f][n][m]);
}
return 0;
}