7.8线性dp学习笔记

线性DP

通天之分组背包

题目背景

直达通天路·小 A 历险记第二篇

题目描述

01 01 01 背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于 01 01 01 背包,他的物品大致可分为 k k k 组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入格式

两个数 m , n m,n m,n,表示一共有 n n n 件物品,总重量为 m m m
接下来 n n n 行,每行 3 3 3 个数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,表示物品的重量,利用价值,所属组数。

输出格式

一个数,最大的利用价值。

样例 #1

样例输入 #1

45 3
10 10 1
10 5 1
50 400 2

样例输出 #1

10

提示

1 ≤ m , n ≤ 1000 1 \leq m, n \leq 1000 1m,n1000

思路

其实就是01背包的增强版
做法是先枚举每个组和容量,然后在从一个组里选出一个物品来进行动态规划;

code

#include<bits/stdc++.h>
using namespace std;
int v,n,t;
int x, g[205][205], i,j,k, w[10001],z[10001], b[10001], dp[10001];
int main(){
    
    
    cin>>v>>n;
    for(i=1;i<=n;i++){
    
    
        cin>>w[i]>>z[i]>>x;//物品重量,物品价值
        t=max(t,x);//看看一共有多少组
        b[x]++;//该组的物品数量+1
        g[x][b[x]]=i;//该组的这个物品是第i个物品
    }
    for(i=1;i<=t;i++){
    
    //挨组枚举,
        for(j=v;j>=0;j--){
    
    //背包体积,倒着算。
            for(k=1;k<=b[i];k++){
    
    //枚举该组里的物品
                if(j>=w[g[i][k]]){
    
    //还有位置就装
                    dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]);//占一部分体积得到相应的价值
                }
            }
        }
    }
    cout<<dp[v];//体积v的最大价值
    return 0;
}

[HEOI2013]Eden 的新背包问题

题目背景

“ 寄 没 有 地 址 的 信 ,这 样 的 情 绪 有 种 距 离 ,你 放 着 谁 的 歌 曲 ,是 怎 样 的 心 情 。 能 不 能 说 给 我 听 。”

题目描述

失忆的 Eden 总想努力地回忆起过去,然而总是只能清晰地记得那种思念的感觉,却不能回忆起她的音容笑貌。

记忆中,她总是喜欢给 Eden 出谜题:在 valentine’s day 的夜晚,两人在闹市中闲逛时,望着礼品店里精巧玲珑的各式玩偶,她突发奇想,问了 Eden 这样的一个问题:有 n n n 个玩偶,每个玩偶有对应的价值、价钱,每个玩偶都可以被买有限次,在携带的价钱 m m m 固定的情况下,如何选择买哪些玩偶以及每个玩偶买多少个,才能使得选择的玩偶总价钱不超过 m m m,且价值和最大。

众所周知的,这是一个很经典的多重背包问题,Eden 很快解决了,不过她似乎因为自己的问题被飞快解决感到了一丝不高兴,于是她希望把问题加难:多次询问,每次询问都将给出新的总价钱,并且会去掉某个玩偶(即这个玩偶不能被选择),再问此时的多重背包的答案(即前一段所叙述的问题)。

这下 Eden 犯难了,不过 Eden 不希望自己被难住,你能帮帮他么?

输入格式

第一行有一个整数,代表玩偶的个数 n n n,玩偶从 0 0 0 开始编号。

第二行开始后面的 n n n 行,每行三个整数,第 ( i + 2 ) (i + 2) (i+2) 行的整数 a i , b i , c i a_i, b_i, c_i ai,bi,ci,分别表示买一个第 i i i 个玩偶需要的价钱,获得的价值以及第 i i i 个玩偶的限购次数。

接下来的一行有一个整数 q q q,表示询问次数。

接下来 q q q 行,每行两个整数 d i , e i d_i, e_i di,ei,表示每个询问去掉的是哪个玩偶(注意玩偶从 0 0 0 开始编号)以及该询问对应的新的总价钱数。(去掉操作不保留,即不同询问互相独立)。

输出格式

输出 q q q 行,第 i i i 行输出对于第 i i i 个询问的答案。

样例 #1

样例输入 #1

5 
2 3 4 
1 2 1 
4 1 2 
2 1 1 
3 2 3 
5 
1 10 
2 7 
3 4 
4 8 
0 5

样例输出 #1

13 
11 
6 
12 
4

提示

样例解释

一共五种玩偶,分别的价钱价值和限购次数为 ( 2 , 3 , 4 ) (2,3,4) (2,3,4) ( 1 , 2 , 1 ) (1,2,1) (1,2,1) ( 4 , 1 , 2 ) (4,1,2) (4,1,2) ( 2 , 1 , 1 ) (2,1,1) (2,1,1) ( 3 , 2 , 3 ) (3,2,3) (3,2,3)

五个询问,以第一个询问为例。

第一个询问表示的是去掉编号为 1 1 1 的玩偶, 且拥有的钱数为 10 10 10 时可以获得的最大价值,则此时剩余玩偶为 ( 2 , 3 , 4 (2,3,4 (2,3,4), ( 4 , 1 , 2 ) (4,1,2) (4,1,2) ( 2 , 1 , 1 ) (2,1,1) (2,1,1) ( 3 , 2 , 3 ) (3,2,3) (3,2,3),若把编号为 0 0 0 的玩偶买 4 4 4 个(即全买了),然后编号为 3 3 3 的玩偶 买一个,则刚好把 10 10 10 元全部花完,且总价值为 13 13 13。可以证明没有更优的方案了。

注意买某种玩偶不一定要买光。


数据规模与约定
  • 对于 10 % 10\% 10% 的数据,保证 n ≤ 10 n \leq 10 n10
  • 另外存在 20 % 20\% 20% 的数据,保证 n ≤ 100 n \leq 100 n100 c i = 1 c_i = 1 ci=1 q ≤ 100 q \leq 100 q100
  • 另外存在 20 % 20\% 20% 的数据,保证 n ≤ 100 n \leq 100 n100 q ≤ 100 q \leq 100 q100
  • 另外存在 30 % 30\% 30% 的数据,保证 c i = 1 c_i = 1 ci=1
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1000 1 \leq n \leq 1000 1n1000 1 ≤ q ≤ 3 × 1 0 5 1 \leq q \leq 3\times 10^5 1q3×105 1 ≤ a i , b i , c i ≤ 100 1 \leq a_i,b_i,c_i \leq 100 1ai,bi,ci100 0 ≤ d i < n 0 \leq d_i < n 0di<n 0 ≤ e i ≤ 1000 0 \leq e_i \leq 1000 0ei1000

思路

多重背包,从前dp一边,从后dp一边,然后得到两套结果,隔一个相加起来就是答案。

code

#include<bits/stdc++.h>
#define B 1010
using namespace std;
typedef long long ll;
int n, v[B], w[B], p[B], f[B][B], g[B][B], a, b, q;
int main() {
    
    
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> w[i] >> v[i] >> p[i];//输入花费,价值,数量
	for (int i = 1; i <= n; i++) {
    
    
		for (int j = 1; j <= 1000; j++) f[i][j] = f[i - 1][j];//往后挪一个
		int num = p[i];//数量
		for (int k = 1; num; k <<= 1) {
    
    
			if (k > num) k = num;
			num -= k;
			for (int j = 1000; j >= w[i] * k; j--)
				f[i][j] = max(f[i][j], f[i][j - w[i] * k] + v[i] * k);//模板
		}
	}//从前往后dp
	for (int i = n; i >= 1; i--) {
    
    
		for (int j = 1; j <= 1000; j++) g[i][j] = g[i + 1][j];
		int num = p[i];
		for (int k = 1; num; k <<= 1) {
    
    
			if (k > num) k = num;
			num -= k;
			for (int j = 1000; j >= w[i] * k; j--)
				g[i][j] = max(g[i][j], g[i][j - w[i] * k] + v[i] * k);
		}
	}//从后往前dp,这两隔一个加一起就是去掉某一个后的最大价值
	cin >> q;
	while (q--) {
    
    
		cin >> a >> b; int ans = 0;
		for (int i = 0; i <= b; i++)//注意从0开始
			ans = max(ans, f[a][i] + g[a + 2][b - i]);
		cout << ans << endl;
	}
}

[AHOI2009] 中国象棋

题目描述

这次小可可想解决的难题和中国象棋有关,在一个 n n n m m m 列的棋盘上,让你放若干个炮(可以是 0 0 0 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

输入格式

一行包含两个整数 n , m n,m n,m,之间由一个空格隔开。

输出格式

总共的方案数,由于该值可能很大,只需给出方案数模 9999973 9999973 9999973 的结果。

样例 #1

样例输入 #1

1 3

样例输出 #1

7

提示

样例说明

除了 3 3 3 个格子里都塞满了炮以外,其它方案都是可行的,所以一共有 2 × 2 × 2 − 1 = 7 2 \times 2 \times 2-1=7 2×2×21=7 种方案。

数据规模与约定

  • 对于 30 % 30\% 30% 的数据, n n n m m m 均不超过 6 6 6
  • 对于 50 % 50\% 50% 的数据, n n n m m m 至少有一个数不超过 8 8 8
  • 对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 100 1 \leq n,m \leq 100 1n,m100

思路

1. 第 i 行 什 么 都 不 放 。 太 愚 蠢 了 。 1.第i行什么都不放。太愚蠢了。 1.i
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j ] [ k ] f[i][j][k]=f[i][j][k]+f[i-1][j][k] f[i][j][k]=f[i][j][k]+f[i1][j][k]

2. 放 1 个 在 空 的 列 。 根 据 状 态 很 容 易 发 现 空 的 列 是 m − j − k 。 那 么 方 案 显 然 了 : 2.放1个在空的列。根据状态很容易发现空的列是m-j-k。那么方案显然了: 2.1mjk
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j − 1 ] [ k ] × ( m − ( j − 1 ) − k ) f[i][j][k]=f[i][j][k]+f[i-1][j-1][k]\times(m-(j-1)-k) f[i][j][k]=f[i][j][k]+f[i1][j1][k]×(m(j1)k)

3. 放 1 个 在 有 1 个 的 列 。 3.放1个在有1个的列。 3.11
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j + 1 ] [ k − 1 ] × ( j + 1 ) f[i][j][k]=f[i][j][k]+f[i-1][j+1][k-1]\times(j+1) f[i][j][k]=f[i][j][k]+f[i1][j+1][k1]×(j+1)

4. 放 2 个 都 在 空 的 列 。 这 里 就 用 到 排 列 了 , 是 之 前 状 态 乘 以 C ( 之 前 空 的 列 , 2 ) 。 4.放2个都在空的列。这里就用到排列了,是之前状态乘以C(之前空的列,2)。 4.2C(2)
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j − 2 ] [ k ] × C ( m − ( j − 2 ) − k ) f[i][j][k]=f[i][j][k]+f[i-1][j-2][k]\times C(m-(j-2)-k) f[i][j][k]=f[i][j][k]+f[i1][j2][k]×C(m(j2)k)

5. 放 1 个 在 空 的 列 , 1 个 在 有 1 个 的 列 。 同 样 乘 法 原 理 : 5.放1个在空的列,1个在有1个的列。同样乘法原理: 5.1,11
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j ] [ k − 1 ] × j × ( m − j − ( k − 1 ) ) f[i][j][k]=f[i][j][k]+f[i-1][j][k-1]\times j\times (m-j-(k-1)) f[i][j][k]=f[i][j][k]+f[i1][j][k1]×j×(mj(k1))

6. 放 2 个 在 有 1 个 的 列 。 我 一 开 始 愚 蠢 到 忘 记 考 虑 这 种 了 。 6.放2个在有1个的列。我一开始愚蠢到忘记考虑这种了。 6.21
f [ i ] [ j ] [ k ] = f [ i ] [ j ] [ k ] + f [ i − 1 ] [ j + 2 ] [ k − 2 ] × C ( j + 2 ) f[i][j][k]=f[i][j][k]+f[i-1][j+2][k-2]\times C(j+2) f[i][j][k]=f[i][j][k]+f[i1][j+2][k2]×C(j+2)

初 始 化 很 简 单 : f [ 0 ] [ 0 ] [ 0 ] = 1 初始化很简单:f[0][0][0]=1 f[0][0][0]=1

code

#include<iostream>
using namespace std;
long long n,m;
long long dp[101][101][101];
long long C(long long x){
    
    
	return (x*(x-1)/2)%9999973;
}
int main(){
    
    
	cin>>n>>m;
	dp[0][0][0]=1;
	for(long long i=1;i<=n;i++){
    
    
		for(long long j=0;j<=m;j++){
    
    
			for(long long k=0;k<=m-j;k++){
    
    
				dp[i][j][k]=dp[i-1][j][k];
				if(k>=1)dp[i][j][k]+=dp[i-1][j+1][k-1]*(j+1);
				if(j>=1)dp[i][j][k]+=dp[i-1][j-1][k]*(m-j-k+1);
				if(k>=2)dp[i][j][k]+=dp[i-1][j+2][k-2]*(j+2)*(j+1)/2;
				if(k>=1)dp[i][j][k]+=dp[i-1][j][k-1]*j*(m-j-k+1);
				if(j>=2)dp[i][j][k]+=dp[i-1][j-2][k]*C(m-j-k+2);
				dp[i][j][k]%=9999973;//及时取模!
			}
		}
	}
	long long ans=0;
	for(long long i=0;i<=m;i++){
    
    
		for(long long j=0;j<=m;j++){
    
    
			ans=(ans+dp[n][i][j])%9999973;
		}
	}
	cout<<ans%9999973;
}

棋盘分割

题目背景

题目描述

将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的两部分中的任意一块继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的平方和最小。

请编程对给出的棋盘及n,求出平方和的最小值。

输入格式

第1行为一个整数n(1 < n <= 15)。

第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。

输出格式

仅一个数,为平方和。

样例 #1

样例输入 #1

3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3

样例输出 #1

1460

思路

用二维前缀和求出任意矩阵的和;
设置dp[a,b,c,d,e]其中a,b,c,d表示此矩阵的左上坐标和右下坐标,e表示切的位置,统计每次横切纵切的最小值,第n-1刀即为答案。

code

#include<iostream>
#define ff(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int f[15][15][15][15][15];
int n;
int sum[15][15];
int get(int a, int b, int c, int d) {
    
    
	a--, b--;
	int siz = sum[c][d] + sum[a][b] - sum[a][d] - sum[c][b];
	return siz * siz;
}
int main() {
    
    
	cin >> n;//切成 n 块
	n--;//切 n - 1 次
	ff(i, 1, 8) 
		ff(j, 1, 8){
    
    
			cin >> sum[i][j];
			sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];//二维前缀和
		}
	ff(a, 1, 8)
	ff(b, 1, 8)
	ff(c, a, 8)
	ff(d, b, 8) {
    
    
		f[a][b][c][d][0] = get(a, b, c, d);//获得各矩阵的平方
	}
	ff(t, 1, n)
	ff(a, 1, 8)
	ff(b, 1, 8)
	ff(c, a, 8)
	ff(d, b, 8) {
    
    
		int q = 99999999;
		ff(x, a, c - 1)//横着切
		q = min(q, min(f[x + 1][b][c][d][0] + f[a][b][x][d][t - 1], f[x + 1][b][c][d][t - 1] + f[a][b][x][d][0]));
		ff(y, b, d - 1)//竖着切
		q = min(q, min(f[a][y + 1][c][d][0] + f[a][b][c][y][t - 1], f[a][y + 1][c][d][t - 1] + f[a][b][c][y][0]));
		f[a][b][c][d][t] = q;//得到最小的结果
	}
	cout << f[1][1][8][8][n];
}

[SCOI2005]最大子矩阵

题目描述

这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的k个子矩阵不能相互重叠。

输入格式

第一行为n,m,k(1≤n≤100,1≤m≤2,1≤k≤10),接下来n行描述矩阵每行中的每个元素的分值(每个元素的分值的绝对值不超过32767)。

输出格式

只有一行为k个子矩阵分值之和最大为多少。

样例 #1

样例输入 #1

3 2 2
1 -3
2 3
-2 3

样例输出 #1

9

思路

1 < = m < = 2 1<=m<=2 1<=m<=2 分类讨论吧。
m = 1 , d p [ i , j ] m=1 ,dp[i,j] m=1,dp[i,j] 表示前i个数中取出j个矩形的最大和;

m = 2 , f [ i , j , k ] m=2,f[i,j,k] m=2,f[i,j,k] 表示第一列选到第i个数,第二列选到第j个数时,总共k个子矩形的答案。

转移方程还挺好理解的,把数字带到定义里看看就懂。

code

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
const int M = 11;
int n, m, K, s1[N], s2[N], dp[N][M], f[N][N][M];
int main() {
    
    
	cin >> n >> m >> K;
	if (m == 1) {
    
    
		for (int i = 1, x; i <= n; i++) {
    
    
			cin >> x;
			s1[i] = s1[i - 1] + x;
		}
		for (int k = 1; k <= K; k++) {
    
    
			for (int i = 1; i <= n; i++) {
    
    
				dp[i][k] = dp[i - 1][k];
				for (int j = 0; j < i; j++) dp[i][k] = max(dp[i][k], dp[j][k - 1] + s1[i] - s1[j]);
			}
		}
		cout << dp[n][K];
	} else {
    
    
		for (int i = 1, x, y; i <= n; i++) {
    
    
			cin >> x >> y;
			s1[i] = s1[i - 1] + x, s2[i] = s2[i - 1] + y;
		}
		for (int k = 1; k <= K; k++) {
    
    
			for (int i = 1; i <= n; i++) {
    
    
				for (int j = 1; j <= n; j++) {
    
    
					f[i][j][k] = max(f[i - 1][j][k], f[i][j - 1][k]);
					for (int l = 0; l < i; l++) f[i][j][k] = max(f[i][j][k], f[l][j][k - 1] + s1[i] - s1[l]);
					for (int l = 0; l < j; l++) f[i][j][k] = max(f[i][j][k], f[i][l][k - 1] + s2[j] - s2[l]);
					if (i == j)  for (int l = 0; l < i; l++) f[i][j][k] = max(f[i][j][k], f[l][l][k - 1] + s1[i] - s1[l] + s2[j] - s2[l]);
				}
			}
		}
		cout << f[n][n][K];
	}
}

猜你喜欢

转载自blog.csdn.net/skyflying266/article/details/125697094