基础DP整理(二)

A - 免费馅饼 (HDU - 1176)

在这里插入图片描述
  用a[i][j]数组表示在第j秒的时候馅饼掉在了i - 1的位置上(下标总体偏移了,便于后续状态转移方程的表示),假设最后掉馅饼的时间为t,用dp[i][j]表示从第i个位置第j个时间段到t时间段能收获馅饼的最大值,利用逆推的思想,状态转移方程:dp[i][j] = max(max(dp[i][j + 1], dp[i - 1][j + 1]), dp[i + 1][j + 1]) + a[i][j];最后dp[6][0]就是最开始从坐标5出发,达到 t 时刻,每一秒只能移动一个位置,收获馅饼个数的最大值。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int a[15][100005], dp[15][100005];
int main(){
    int n, p, q, t;
    while(scanf("%d", &n)){
        if(n == 0) break;
        t = 0;
        memset(a, 0, sizeof a);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; i++){
            scanf("%d%d", &p, &q);
            a[p + 1][q]++;
            if(t < q) t = q;
        }
        for(int i = 1; i <= 11; i++) dp[i][t] = a[i][t];
        for(int j = t - 1; j >= 0; j--){
            for(int i = 1; i <= 11; i++){
                dp[i][j] = max(max(dp[i][j + 1], dp[i - 1][j + 1]), dp[i + 1][j + 1]) + a[i][j];
            }
        }
        cout << dp[6][0] << '\n';
    }
    return 0;
}

B - (串长<=500)(HDU - 1159)

在这里插入图片描述
  这道题是动态规划中最基本的最长公共子序列问题,最原始的做法(不加什么优化,但能ac)是嵌套两个for循环,枚举两个字符串,dp[i][j]表示字符串a前i个字符、字符串b前j个字符的最长公共子序列,状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); 如果a[i] == b[j],还有一种情况,此时还有状态转移方程:dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);最终dp[n][m]即为所求。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
char a[1005], b[1005];
int dp[1005][1005];
int main(){
//    ios::sync_with_stdio(false);
//    cin.tie(0), cout.tie(0);
    while(scanf("%s %s", a + 1, b + 1) != EOF){
        int n = strlen(a + 1);
        int m = strlen(b + 1);
        debug(m);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                if(a[i] == b[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
            }
        }
        cout << dp[n][m] << '\n';
    }
    return 0;
}

C - (序列长度<=1000)(HDU - 1257)

在这里插入图片描述

  这道题我不是用动态规划做的,我是用类似于贪心的做法,如果新来了一颗导弹,我就遍历所有导弹拦截系统,如果有能拦截它的导弹拦截系统(某个导弹拦截系统能拦截的高度比新来的导弹高),则更新这个拦截系统能拦截的最大高度;如果找不到,就再配置一个新的导弹拦截系统,放在所有拦截系统的后边。这样就保证了所有拦截系统能拦截的高度是从低到高排列的,实现了整体的有序,最后输出拦截系统的个数即可。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int a[100005], n, flag, s, i, j, b;
    while(~scanf("%d", &n)){
        s = 1;
        scanf("%d", &b);
        a[s] = b;
        for(i = 1; i < n; i++){
            flag = 0;
            scanf("%d", &b);
            for(j = 1; j <= s; j++){
                if(a[j] >= b){
                    a[j] = b;
                    flag = 1;
                    break;
                }
            }
            if(flag == 0){
                s++;
                a[s] = b;
            }
        }
        printf("%d\n", s);
    }
    return 0;
}

D - 记忆化搜索 (HDU - 1078)

在这里插入图片描述
  题意:一只老鼠吃奶酪,给你n * n的地图,地图上的数字表示奶酪的数量,老鼠一次能走最多k步,但是每次走到一个地方的奶酪数量要比之前的多,问,老鼠最多吃多少奶酪。
  用dp保存数据和标记,用dfs搜索,起点是固定的,最后输出dfs(0, 0)即可。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int n, m;
int a[105][105], dp[105][105], ans, dx[4] = {0, 0, -1, 1}, dy[4] = {1, -1, 0, 0};
int dfs(int x, int y){
    if(dp[x][y]) return dp[x][y];
    int s = 0;
    for(int i = 0; i < 4; i++){
        for(int j = 1; j <= m; j++){
            int p = x + dx[i] * j, q = y + dy[i] * j;
            if(p >= 0 && q >= 0 && p < n && q < n && a[p][q] > a[x][y]){
                s = max(s, dfs(p, q));
            }
        }
    }
    dp[x][y] = s + a[x][y];
    return dp[x][y];
}
int main(){
    while(cin >> n >> m){
        memset(dp, 0, sizeof dp);
        if(n == -1 && m == -1) break;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cin >> a[i][j];
            }
        }
        cout << dfs(0, 0) << '\n'; 
    }
    return 0;
}

E - 01背包 (HDU - 2602)

在这里插入图片描述
  用一维数组表示最大价值的时候,枚举背包容量要从大到小枚举,因为01背包物品只有一个,容量是j - v[i]的价值f[j - v[i]]是前i - 1个物品中的价值,满足题意每个物品只能取一次的条件。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define inf 1e9 + 5 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
} 
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int v[1005], w[1005], dp[1005]; 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        memset(dp, 0, sizeof dp);
        int n, m;
        cin >> n >> m;
        for(int i = 1; i <= n; i++){
            cin >> w[i];
        }
        for(int j = 1; j <= n; j++){
            cin >> v[j];
        }
        for(int i = 1; i <= n; i++){
            for(int j = m; j >= v[i]; j--){
                dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
        cout << dp[m] << '\n';
    }
    return 0;
}

F - 完全背包 (HDU - 1114)

在这里插入图片描述
  完全背包问题跟01背包问题对容量的枚举方式不同,满足了每个物品可以取无限个的条件。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define inf 1e9 + 5 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
} 
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
const int INF = 0x3f3f3f3f;
int v[10005], w[10005], dp[10005]; 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        memset(v,0,sizeof(v));
        memset(w,0,sizeof(w));
        memset(dp, INF, sizeof dp);
        int a, b, ans;
        cin >> a >> b;
        ans = b - a;
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++){
            cin >> v[i] >> w[i];
        }
        dp[0] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = w[i]; j <= ans; j++){
                dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
            }
        }
        if(dp[ans] != INF) cout << "The minimum amount of money in the piggy-bank is " << dp[ans] << "." << '\n';
        else cout << "This is impossible.\n";
    }
    return 0;
}

G - 多重背包 (HDU - 2844)

在这里插入图片描述
  用2的n次幂表示每个物品可以取的数量(最后凑不到的2的n次幂的自成一个数),把这几堆物品看成每一个背包,用01背包的做法就可以求得最终结果了。比模板多了唯一的点是查多少数字在题目要求的范围内出现过,枚举一遍看dp[i] == i 的个数即可。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define inf 1e9 + 7 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int v[105], s[105];
int dp[100005];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n, m;
    while(cin >> n >> m){
        memset(dp, 0, sizeof dp);
        if(n == 0 && m == 0) break;
        for(int i = 1; i <= n; i++) cin >> v[i];
        for(int i = 1; i <= n; i++) cin >> s[i];
        for(int i = 1; i <= n; i++){
            int x[105], cnt = 0, u = 1;
            while(1){
                if(s[i] >= u){
                    x[++cnt] = u;
                    s[i] -= u;
                    u *= 2;
                }
                else break;
            }
            if(s[i] > 0) x[++cnt] = s[i];
            for(int k = 1; k <= cnt; k++){
                for(int j = m; j >= v[i] * x[k]; j--){ 
                    dp[j] = max(dp[j], dp[j - v[i] * x[k]] + v[i] * x[k]);
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= m; i++){
            if(dp[i] == i) ans++;
        }
        cout << ans << '\n';
    }
    return 0;
}

H - 背包问题1 (LibreOJ - 10179)

在这里插入图片描述
  这道题也是多重背包问题,我的做法跟前面那道题一样。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define inf 1e9 + 7
void debug_out() {
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
const int INF = 0x3f3f3f3f;
int n, m, b[205], c[205], dp[20005];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    memset(dp, INF, sizeof dp);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    cin >> m;
    memset(dp, INF, sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i++) {
        int x[205], u = 1, cnt = 0;
        while (1) {
            if (c[i] >= u) {
                x[++cnt] = u;
                c[i] -= u;
                u *= 2;
            } else
                break;
        }
        if (c[i] > 0) x[++cnt] = c[i];
        for (int k = 1; k <= cnt; k++) {
            for (int j = m; j >= x[k] * b[i]; j--) {
                dp[j] = min(dp[j], dp[j - x[k] * b[i]] + x[k]);
            }
        }
    }
    cout << dp[m];
    return 0;
}

I - 背包问题2 (CodeForces - 1458B)

在这里插入图片描述
  题目大意:输入一个n,表示有n个瓶子。接下来有n行,每行第一个数代表该瓶子最大的容量,第二个数表示该瓶子当前的水量。你可以将一个瓶子的水倒入另一个瓶子中,假设到S的水,则另一个瓶子的当前容量应该为min(a[i], b[i] + s / 2),因为每次倒水都会损失一半,输出n个数,第 i 个数表示选其中的 i 个瓶子,这几个瓶子的含水量和最大是多少。
  这是道背包DP问题。每个瓶子有选和不选两种状态,其他瓶子可以往这几个瓶子里倒水。一维的状态可以表示为f[i][j][k],表示前i个瓶子,从里面选j个,容量恰好为k时,水量的最大值是多少,在当前状态下,状态转移方程可以表示为:f[i][j][k] = f[i - 1][j - 1][k - a[i]] + b[i],当前状态只与上一层有关,所以可以优化掉一维,f[i][j] 表示前n个瓶子,选 i 个,当前容量为 j 时的水量最大值。就可以推出方程:f[i][j] = f[i - 1][j - a[i]] + b[i]。这个问题其实可以抽象成二维费用的背包问题,第一维的费用永远是1,也就是个数,第二维的费用是容量,所以直接拿二维费用的板子写,因为这里用的是恰好,所以把f[0][0] 初始化为0,其他为 -INF 即可。
  为什么用恰好来表示状态,因为题目还有一个限制条件,输出选择 i 个的最大水量和,预处理一个A和B,表示容量总和和已有的水量总和,对于每个 i ,j从0 -> A, 每次res = max(res, min(j, f[i][j] + (B - f[i][j]) / 2)),整理一下,res = max(res, min(j, (B + f[i][j]) / 2)),然后每个 i 输出对应的res就好了。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int ansa, ansb;
int dp[105][100005], a[105], b[105];
int main(){
//    ios::sync_with_stdio(false);
//    cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i] >> b[i];
		ansa += a[i], ansb += b[i];
	}
	memset(dp, -0x3f, sizeof dp);
	dp[0][0] = 0;
	for(int i = 1; i <= n; i++){
		for(int j = i ; j >= 1; j--){
			for(int k = ansa; k >= a[i]; k--){
				dp[j][k] = max(dp[j][k], dp[j - 1][k - a[i]] + b[i]);
			}
		}
	}
	for(int i = 1; i <= n; i++){
		double ans = 0;
		for(int j = 0; j <= ansa; j++){
			ans = max(ans, min(1.0 * j, 0.5 * (ansb + dp[i][j])));
		}
		printf("%.10lf ", ans);
	}
	return 0;
}

J - 区间dp1 (LibreOJ - 10147)

在这里插入图片描述
这道题是区间dp的版题,详情可以看我之前写的博客:区间DP
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define inf 1e9 + 7 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
int a[405], s[405], f1[405][405], f2[405][405];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
    	cin >> a[i];
    	s[i] = s[i - 1] + a[i];
	}
    for(int i = n + 1; i < 2 * n; i++){
    	s[i] = a[i - n] + s[i - 1];
	}
	for(int len = 2; len <= n; len++){
		for(int i = 1; i < 2 * n - len + 1; i++){
			int l = i;
			int r = i + len - 1;
			f1[l][r] = 1e8;
			f2[l][r] = 0;
			for(int k = l; k < r; k++){
				f1[l][r] = min(f1[l][r], f1[l][k] + f1[k + 1][r] + s[r] - s[l - 1]);
				f2[l][r] = max(f2[l][r], f2[l][k] + f2[k + 1][r] + s[r] - s[l - 1]);
			}
		}
	} 
	int minx = 1e8, maxx = 0;
	for(int i = 1; i <= n; i++){
		minx = min(minx, f1[i][i + n - 1]);
		maxx = max(maxx, f2[i][i + n - 1]);
	}
	cout << minx << '\n' << maxx;
    return 0;
}

K - 区间dp2 (黑暗爆炸 - 1260)

在这里插入图片描述
  区间dp问题,开一个二维dp数组,dp[i][j] 代表区间从 i 到 j 最小染色次数。如果区间两端颜色相同 s[i] == s[j] 的话,dp[i][j] = min(dp[i][j - 1], dp[i + 1][j])。颜色不同 s[i] != s [j]的话,枚举中间端点k,把此区间分为两部分,转换为子问题求解即可。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
char a[55];
int dp[55][55];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
	cin >> a + 1;
	int s = strlen(a + 1);
	memset(dp, 0x3f, sizeof dp);
	for(int len = 1; len <= s; len++){
		for(int l = 1; l <= s - len + 1; l++){
			int r = l + len - 1;
			if(len == 1) dp[l][r] = 1;
			else if(a[l] == a[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]);
			else{
				for(int k = l; k < r; k++){
					dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]);
				}
			}
		}
	}
	cout << dp[1][s];
    return 0;
} 

L - 区间dp3 (HDU - 4283)

在这里插入图片描述
  题目大意:有 n 个人,每个人有一个D[i]值,如果第 i 个人排在第 k 位置,则他的愤怒值就为D[i] *(k - 1),等待过程中有一个黑屋子,可以把人暂时放到黑屋子里,使总的愤怒值最小。
  这是一道区间dp题,我们用dp[i][j]表示第i个人到第j个人unhappiness总和的最小值,因此dp[1][n]就是我们所要求的的值。怎么求呢?这种题肯定是转换成一些子问题,不断地的递推求解。我们来考虑第i个人的情况,在区间[i, j]中,第i个人可能是第一个走的,也可能是第j - i + 1个走的,所以假设第i个人是第k个走的,根据题目有第i个人的这里写代码片unhappiness值为(k - 1) * D[i]。那么从i + 1开始的k - 1个人都是在i 之前走的,此时就会出现一个子问题dp[i + 1][i + 1 + k - 1 + 1]。这个区间的人都是已经走过的,那么在[i, j] 区间里,从i + k到j都是还没有走的,那么他们就要继续等待,把它们看成一个整体,即unhappyiness 值为k * (sum[j] - sum[i + k - 1])。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
#define INF 0x3f3f3f3f
int a[105], s[105], dp[105][105];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    for(int u = 1; u <= t; u++){
        memset(s, 0, sizeof s);
        memset(dp, 0, sizeof dp);
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++){
            cin >> a[i];
            s[i] += s[i - 1] + a[i];
        }
        for(int i = 1; i <= n; i++){
            for(int j = i + 1; j <= n; j++){
                dp[i][j] = INF;
            }
        }
        for(int len = 1; len <= n; len++){
            for(int i = 1; i <= n - len + 1; i++){
                int j = i + len - 1;
                for(int k = i; k <= j; k++){
                    dp[i][j] = min(dp[i][j], dp[i + 1][k] + (k - i) * a[i] + (k - i + 1) * (s[j] - s[k]) + dp[1 + k][j]);
                } 
            }
        } 
        cout << "Case #" << u << ": " << dp[1][n] << endl; 
    }
    return 0;
}

M - 状压dp1 (CodeForces - 1051D)

在这里插入图片描述
  第 i 列的状态有四种:(黑,黑),(黑,白),(白,黑),(白,白),设为0(0, 0), 1(0, 1), 2(1, 0), 3(1, 1)。dp[i][k][j]:i 表示第 i 列,k 表示有 k 种,j 表示第 i 列的状态。
  那么我们可以得到:dp[i][k][0] = dp[i - 1][k][0] + dp[i - 1][k][1] + dp[i - 1][k][2] + dp[i - 1][k-1][3]。
  同理:dp[i][k][3] = dp[i - 1][k][3] + dp[i - 1][k][1] + dp[i - 1][k][2] + dp[i - 1][k - 1][0]。
  因为第 i 列的状态是纯色的,只要上一列中有和这一列一样的颜色就可以叠加并且 k 不变,如果和上一个完全不一样,则种类会多加一个,所以要取k - 1。
dp[i][k][1] = dp[i-1][k][1] + dp[i-1][k-2][2] + dp[i-1][k-1][0] + dp[i-1][k-1][3]。
  同理:dp[i][k][2] = dp[i-1][k][2] + dp[i-1][k-2][1] + dp[i-1][k-1][0] + dp[i-1][k-1][3]。
  因为第 i 列的状态是杂色的,所以要考虑如果上一个和当前状态一样则加上,如果完全不一样则要找k - 2的状态加上,如果是纯色会多一种,则找k - 1的状态加上。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
const int mod = 998244353;
LL dp[1005][2005][4]; 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
	int n, k;
	cin >> n >> k;
	dp[1][1][0] = 1;
	dp[1][2][1] = 1;
	dp[1][2][2] = 1;
	dp[1][1][3] = 1;
	debug(dp[n][k][1]);
	for(int i = 2; i <= n; i++){
		dp[i][1][1] = dp[i - 1][1][1];
		dp[i][1][0] = dp[i - 1][1][0];
		dp[i][1][2] = dp[i - 1][1][2];
		dp[i][1][3] = dp[i - 1][1][3]; 
		for(int j = 2; j <= k; j++){
			dp[i][j][0] = (dp[i - 1][j][0] + dp[i - 1][j][1] + dp[i - 1][j][2] + dp[i - 1][j - 1][3]) % mod;
			dp[i][j][1] = (dp[i - 1][j][1] + dp[i - 1][j - 1][0] + dp[i - 1][j - 2][2] + dp[i - 1][j - 1][3]) % mod;
			dp[i][j][2] = (dp[i - 1][j][2] + dp[i - 1][j - 1][0] + dp[i - 1][j - 2][1] + dp[i - 1][j - 1][3]) % mod;
			dp[i][j][3] = (dp[i - 1][j][3] + dp[i - 1][j - 1][0] + dp[i - 1][j][1] + dp[i - 1][j][2]) % mod;
		}
	}
	int ans;
	ans = (dp[n][k][0] + dp[n][k][1] + dp[n][k][2] + dp[n][k][3]) % mod;
	cout << ans;
    return 0;
}

N - 状压dp2 (LibreOJ - 10170)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  因为枚举i - 1层的状态和第i层的状态所需的循环过多导致时间复杂度很高,所以在这里我们运用预处理的方式来解决此题,用st数组来存储不存在连续个1的合法方案数,用head数组来存储与合法方案数进行’|’和’&’运算后的合法方案数。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
const int N = 12, M = 1 << 10, K = 105;
vector<int> st;
int cnt[M], n, k;
vector<int> h[M];
LL f[N][K][M];
int check(int st){
	for(int i = 0; i < n; i++){
		if((st >> i & 1) && (st >> i + 1 & 1)) return 0;
	}
	return 1; 
}
int count(int st){
	int ans = 0;
	for(int i = 0; i < n; i++) ans += st >> i & 1;
	return ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
	cin >> n >> k;
	for(int i = 0; i < 1 << n; i++){
		if(check(i)){
			st.push_back(i);
			cnt[i] = count(i);
 		}
	}
	for(int i = 0; i < st.size(); i++){
		for(int j = 0; j < st.size(); j++){
			int a = st[i], b = st[j];
			if((a & b) == 0 && check(a | b)) h[i].push_back(j);
		}
	}
	f[0][0][0] = 1;
	for(int i = 1; i <= n + 1; i++){
		for(int j = 0; j <= k; j++){
			for(int a = 0; a < st.size(); a++){
				for(int b: h[a]){
					int c = cnt[st[a]];
					if(j >= c) f[i][j][st[a]] += f[i - 1][j - c][st[b]];
				}
			}
		}
	}
	cout << f[n + 1][k][0] << '\n';
    return 0;
}

O - 状压dp3 (HDU - 1400)

在这里插入图片描述
  集合:前i - 1列已经摆好,前i - 1列摆放的横着的砖头延伸到第i列的状态j的方案数(第i - 1列为砖块的起始位置)
  条件:1、空格必须是偶数个 2、不能存在冲突
  用k来表示第i - 2列的砖块延伸到第i - 1列的状态,j来表示从第i - 1列延伸到第i列的方案,(因为砖块横着摆放的长度是2,所以k也就是把砖块头放在第i - 2列的状态,因为长度为2,所以砖块尾会延伸到第i - 1列,j的表示也是同理)。如果此时j的状态是已经固定了的,即前i -1 列摆放着的横着的砖头延伸到第i列的状态已经确定了,但是第i - 2列横着摆放的砖头延伸到第i - 1列的状态还没有确定,那么我们就开始枚举第i - 2列摆放横着的砖头延伸到第i - 1列的状态,即k的状态,这里我们用二进制来表示k的状态,因为横着的砖头摆放完后,便要摆放竖着的砖头,所以必须要确保能装下竖着的摆放砖头。因为竖着的砖头的长度为1,所以横着摆放砖头的状态中间必须保证要有偶数个空位才行。
  首先第i - 1列存有的砖头是j和k状态的叠加,因为砖块头和砖块尾巴都在第i - 1列上,又因为j和k都是用二进制表示的,所以j和k的二进制数的1叠加后(即进行异或操作后),必须存有连续偶数个0,如果符合条件那么就可以证明能从第i - 2列延伸到第i - 1列的状态k可以转移到从第i - 1列延伸到第i列的状态k,所以此时可以得到状态转移方程:f[i][j] = f[i][j] + f[i - 1][k];
  为什么不需要判断j和k的状态呢?
  因为第f[i - 1][k]表示的是前i - 2列已经摆好,且前i - 2列摆放的横着的砖头延伸到第i - 2列的状态j的方案数,所以f[i - 1][k]存的必定是合法的方案数,所以第i - 2行的空位肯定是偶数位。(f[i - 1, k]状态判断的合法条件是第i - 3列的状态k与第i - 1列的状态j的在第i - 2列中的异或值的连续零为偶数)。那么j呢,如果第i列枚举完后,那么就开始判断第i + 1列了,因为第i + 1列判断的是第i列中第i - 1列延伸到第i的砖块的状态和第i列延伸到第i + 1列的状态的异或值不发生冲突,所以第 j 也无需判断。
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
#define m_p make_pair
const int N = 12, M = 1 << N;
LL f[N][M];
int st[M];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n, m;
    while(cin >> n >> m){
        if(n == 0 && m == 0) break;
        for(int i = 0; i < 1 << n; i++){
            int cnt = 0;
            st[i] = 1;
            for(int j = 0; j < n; j++){
                if(i >> j & 1){
                    if(cnt & 1){
                        st[i] = 0;
                        break;
                    }
                    cnt = 0;
                }
                else cnt++; 
            }
            if(cnt & 1) st[i] = 0; 
        }
        memset(f, 0, sizeof f); 
        f[0][0] = 1;
        for(int i = 1; i <= m; i++){
            for(int j = 0; j < 1 << n; j++){
                for(int k = 0; k < 1 << n; k++){
                    if((j & k) == 0 && st[j | k]) f[i][j] += f[i - 1][k];
                }
            }
        }
        cout << f[m][0] << '\n';
    }
    return 0;
}

P - 状压dp4 (CodeForces - 1209E1)

在这里插入图片描述
具体的实现代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
 
void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif
const int N = 205;
int a[N][N], dp[N][20];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
	int t, n, m;
	cin >> t;
	while(t--){
		memset(dp, 0, sizeof dp);
		cin >> n >> m;
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++){
				cin >> a[i][j];
			}
		}
		for(int i = 1; i <= m; i++){
			for(int s = 0; s < (1 << n); s++){
				for(int k = 1; k <= n; k++){
					int c = 0;
					for(int j = 1; j <= n; j++){
						if(s & (1 << j - 1)) c += a[j][i];
					}
					for(int p = 0; p < (1 << n); p++){
						if(!(s & p)) dp[i][s | p] = max(dp[i][s | p], dp[i - 1][p] + c);
					}
					for(int j = 1; j <= n; j++){
						a[j - 1][i] = a[j][i];
					}
					a[n][i] = a[0][i];
				}
			}
		}
		cout << dp[m][(1 << n) - 1] << '\n';
	}
    return 0;
}

Q - Gurdurr (Gym - 102341G)

在这里插入图片描述
  这道题是2019年ccpc秦皇岛站的题目,具体涉及到基于连通性状态压缩的动态规划(轮廓线DP / 插头DP),这类DP的做法请见我另外一篇博客:插头DP
  具体代码有时间再补…

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值