背包问题学习指南

前置芝士

01背包

朴素法

时间复杂度:O(n*m)

[数组定义]

f[i][w]的定义如下:对于前i个物品,当前背包的容量为w,这种情况下可以装的最大价值是f[i][w]。

int w[110],c[110],f[110][110];
int n,m;
int dp(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(j<w[i])
                f[i][j]=f[i-1][j];
            else{
                f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
            }
        }
    return f[n][m];
}

状态压缩(滚动数组)

01背包内嵌的循环是从⼤到⼩遍历,为了保证每个物品仅被添加⼀次

int w[110],c[110],f[110];
int n,m;
int dp(){
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--){
                f[j]=max(f[j],f[j-w[i]]+c[i]);
            }
    return f[m];
}

二维01背包

完全背包

完全背包的物品是可以添加多次的,所以可以而且需要从⼩到⼤去遍历

多重背包

朴素算法

时间复杂度:\(O(W\sum_{i=1}^{n}k_{i})\)

将多重背包转化为01背包进行求解,把第i种物品转换成\(s_i\)件01背包中的物品,每件物品的体积为\(k*v_i\),价值为\(k*w_i\)\((0<=k<=s_i)\)

const int N=110;
int v[N],w[N],s[N];//体积,价值,数量
void solve() {
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
                f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
            }
        }
    }
}

二进制优化

时间复杂度:\(O(W\sum_{i=1}^n\log_2k_i)\)

将第i种物品拆分成若干件物品,每件物品的体积和价值乘以一个拆分系数\((1,2^1,2^2...,2^{k-1},s_i-2^{k}+1)\),就可以转化成01背包的物品求解了。

const int N=110;
int v[N],w[N],s[N];//体积,价值,数量
int vv[N],ww[N],num=1;
void solve() {
    for(int i=1;i<=n;i++){
        cin>>v>>w>>s;
        for(int j=1;j<=s;j<<=1){
            vv[num]=j*v;
            ww[num++]=j*w;
            s-=j;
        }
        if(s){
            vv[num]=s*v;
            ww[num++]=s*w;
        }
    }
}

单调队列优化

时间复杂度:O(mn) [m:总容量,n为商品种类]

可以通过n=1000,m=1000的数据量

const int N = 110;
const int M = 40010;
int f[M];
int g[M];
int n, m;
int v, w, s;
int q[M*2];
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		memcpy(g, f, sizeof(f));//f备份到g中
		cin >> w>>v >> s;//价值,体积,数量
		for (int j = 0; j < v; j++) {//拆分成v个类
			int h = 0, t = -1;
			for (int k = j; k <= m; k += v) {//对每个类使用单调队列
				if (h <= t && q[h] < k - s * v) h++;//q[h]不在窗口[k-s*v,s-v]内,队头出队
				if (h <= t) f[k] = max(g[k], g[q[h]] + (k - q[h]) / v * w);
				//(k-q[h])/v:表示还能放入的物品个数
				//使用对头最大值跟新f
				while (h <= t && g[k] >= g[q[t]] + (k - q[t]) / v * w) t--;
				//如果g[k]比用g[q[t]]更新f[x],获得更大的价值,则头出队
				//下标入队
				q[++t] = k;
			}
		}
	}
	cout << f[m] << endl;
}

分组背包

朴素法

物品-体积-决策

int f[110][110],v[110][110],s[110],w[110][110];
int n,V;
int solve(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=V;j++)
            for(int k=0;k<=s[i];k++){
                if(j>=v[i][k])
                    f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
            }
        return f[n][V];
}

状态压缩(滚动数组)

物品-体积-决策

int f[110],v[110],w[110];
int s;
int solve(){
    for(int i=1;i<=n;i++){
        cin>>s;
    for(int j=1;j<=s;j++) cin>>v[j]>>w[j];
        for(int j=V;j>=1;j--){
            for(int k=0;k<=s;k++){
                if(j>=v[k])
                    f[j]=max(f[j],f[j-v[k]]+w[k]);
            }
        }  
    }
    return f[V];
}

A+B Problem

[problem description]

给定一个正整数n,求将其分解成若干个素数之和的方案总数。

[input]

一行一个正整数 \(n\)

[output]

一行一个整数表示方案总数。

[sample]

7
3

存在如下三种方案:

  • \(7=7\)
  • \(7=2+5\)
  • \(7=2+2+3\)

[datas]

\(1\le n\le 10^3\)

[solved]

0-1背包

int n;
ll f[1010];
int pr[1010];
int idx;
bool vis[1010];
void solve() {
	cin>>n;
	for(int i=2;i<=n;i++){
		if(!vis[i]){
			pr[idx++]=i;
		}
		for(int j=0;j<idx&&pr[j]*i<=n;j++){
			vis[pr[j]*i]=true;
			if(i%pr[j]==0) break;
		}
	}
	f[0]=1;
	for(int i=0;i<idx;i++){
		for(int j=pr[i];j<=n;j++){
				f[j]+=f[j-pr[i]];
		}
	}
	cout<<f[n]<<endl;
}

排兵布阵

[problem description]

小 C 正在玩一款排兵布阵的游戏。在游戏中有 n 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有m 名士兵,可以向第 i 座城堡派遣 \(a_i\) 名士兵去争夺这个城堡,使得总士兵数不超过 m

如果一名玩家向第 i 座城堡派遣的士兵数严格大于对手派遣士兵数的两倍,那么这名玩家就占领了这座城堡,获得 i 分。

现在小 C 即将和其他 s 名玩家两两对战,这 s 场对决的派遣士兵方案必须相同。小 C 通过某些途径得知了其他 s 名玩家即将使用的策略,他想知道他应该使用什么策略来最大化自己的总分。

由于答案可能不唯一,你只需要输出小 C 总分的最大值。

[input]

输入第一行包含三个正整数 s,n,m,分别表示除了小 C 以外的玩家人数、城堡数和每名玩家拥有的士兵数。
接下来 s 行,每行 n 个非负整数,表示一名玩家的策略,其中第 i 个数 \(a_i\) 表示这名玩家向第 i 座城堡派遣的士兵数。

[output]

输出一行一个非负整数,表示小 C 获得的最大得分。

[datas]

1≤s≤100
1≤n≤100
1≤m≤20000

对于每名玩家 \(a_i≥0,\)\(∑^{n}_{i=1}a_i≤m\)

[solved]

const int inf = 0x3f3f3f3f;
const ll dinf = 0x7f7f7f7f;
//const int N=100010;

/*
 dp[i][j]第i个城堡时,已派出j个士兵
 a[i][j]第i个城堡,第j个人出的兵
 */
int s, n, m;
int f[20010], a[110][110], res;
void solve() {
	cin >> s >> n >> m;
	for (int i = 1; i <= s; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> a[j][i];
		}
	}
	for (int i = 1; i <= n; i++) {
		sort(a[i] + 1, a[i] + 1 + s);
	}
	for (int i = 1; i <= n; i++)
		for (int j = m; j >= 1; j--)
			for (int k = 1; k <= s; k++) {
				if (j > a[i][k] * 2)
					f[j] = max(f[j- a[i][k] * 2 - 1] + k * i, f[j]);
			}
	cout << f[m] << endl;
}

宝物筛选

[problem description]

终于,破解了千年的难题。小 FF 找到了王室的宝物室,里面堆满了无数价值连城的宝物。

这下小 FF 可发财了,嘎嘎。但是这里的宝物实在是太多了,小 FF 的采集车似乎装不下那么多宝物。看来小 FF 只能含泪舍弃其中的一部分宝物了。

小 FF 对洞穴里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小 FF 有一个最大载重为 \(W\) 的采集车,洞穴里总共有 \(n\) 种宝物,每种宝物的价值为 \(v_i\),重量为 \(w_i\),每种宝物有 \(m_i\) 件。小 FF 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。

[input]

第一行为一个整数 \(n\)\(W\),分别表示宝物种数和采集车的最大载重。

接下来 \(n\) 行每行三个整数 \(v_i,w_i,m_i\)

[output]

输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。

[sample]

in

4 20
3 9 3
5 9 1
9 4 2
8 1 3

out

47

[datas]

\(n\leq \sum m_i \leq 10^5\)\(0\le W\leq 4\times 10^4\)\(1\leq n\le 100\)

[solved]

二进制优化多重背包

(1)将一个整数n划分为二进制数,这些二进制数之间可以通过任意求和将[1,n]内的整数表示出来。

const int N=110;
int v,w,s;
int vv[100010],ww[100010],num=0;
int f[40010];
int n,m;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>v>>w>>s;
		for(int j=1;j<=s;j<<=1){
			vv[++num]=j*v;
			ww[num]=j*w;
			s-=j;
		}if(s){
			vv[++num]=s*v;
			ww[num]=s*w;
		}
	}
	for(int i=1;i<=num;i++){
		for(int j=m;j>=ww[i];j--){
			f[j]=max(f[j],f[j-ww[i]]+vv[i]);
		}
	}
	cout<<f[m]<<endl;
}
posted @ 2023-10-27 12:50  White_Sheep  阅读(4)  评论(0编辑  收藏  举报