区间dp 解题报告

这篇具有很好参考价值的文章主要介绍了区间dp 解题报告。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

**区间dp:**就是对于区间的一种动态规划,对于某个区间,它的合并方式可能有很多种,我们需要去枚举所有的方式,通常是去枚举区间的分割点,找到最优的方式(一般是找最少消耗)。

区间dp写法:(

for(int len = 2; len <= n; ++len) {
	for(int i = 1; i + len - 1 <= n; ++i) {
		int j = i + len - 1;
			for(int k = i; k < j; ++k) {
				状态转移函数
			}
	}
}
石子合并(弱化版)

问题描述:略。

转移方程:
F ( i , j ) = m i n i ≤ k ≤ j F ( i , j ) , F ( i , k ) + F ( k + 1 , j ) + ∑ i ≤ z ≤ j A [ z ] F(i, j ) = min_{i \leq k \leq j }{F(i,j), F(i,k) + F(k+1, j) + \sum_{i \leq z \leq j}{A[z]}} F(i,j)=minikjF(i,j),F(i,k)+F(k+1,j)+izjA[z]
状态描述:

F(i,j)表示由i到j堆成一堆的最小代价。

边界:
F ( i , j ) = { 0 i f i = = j i n f i f i ! = j F(i,j) = \begin{cases} 0 \quad if \quad i == j \\ inf \quad if \quad i != j \end{cases} F(i,j)={0ifi==jinfifi!=j

目标:
F ( 1 , N ) F(1,N) F(1,N)
代码:

void solve() {
    int n; cin>>n;
    vector<int> a(n+1), sum(n+1);
    vector<vector<int>> f(n+1, vector<int>(n+1, INF));
    for(int i = 1; i <= n; ++i) cin>>a[i], sum[i] = sum[i-1] + a[i];
    for(int i = 1; i <= n; ++i) f[i][i] = 0;
    for(int len = 2; len <= n; ++len) {
        for(int i = 1; i + len - 1 <= n; ++i) {
            int j = i + len - 1;
            for(int k = i; k < j; ++k) {
                f[i][j] = min(f[i][j], f[i][k] + f[k+1][j] + sum[j] - sum[i-1]);
            }
        }
    }
    cout<<f[1][n];
}
石子合并

问题描述:环形石子合并,求最小和最大代价。

转移方程:

​ 与上面类似。环状dp。

代码:

void solve() {
    int n; cin>>n;
    vector<int> a(n*2 + 1), sum(a);
    for(int i = 1; i <= n; ++i) cin>>a[i], a[i + n] = a[i];
    for(int i = 1; i <= n * 2; ++i) sum[i] = sum[i - 1] + a[i];
    vector<vector<int>> fma(n*2+ 1, vector<int>(n*2 + 1)), fmi(n*2 + 1, vector<int>(n*2 + 1, INF));
    for(int i = 1; i <= n*2; ++i) fmi[i][i] = 0;
    for(int len = 2; len <= n; ++len) {
        for(int i = 1; i + len - 1 <= n * 2; ++i) {
            int j = i + len - 1;
            for(int k = i; k < j; ++k) {
                fmi[i][j] = min(fmi[i][j], fmi[i][k] + fmi[k+1][j] + sum[j] - sum[i - 1]);
                fma[i][j] = max(fma[i][j], fma[i][k] + fma[k+1][j] + sum[j] - sum[i - 1]);
            }
        }
    }
    int ma = -INF;
    int mi = INF;
    for(int i = 1; i <= n; ++i) {
        ma = max(ma, fma[i][i+n-1]);
        mi = min(mi, fmi[i][i+n-1]);
    }
    cout<<mi<<'\n'<<ma;
}
// 错误的,但是却AC了 (
void solve() {
    int n; cin>>n;
    vector<int> a(2 * n + 1), sum(a);
    for(int i = 1; i <= n; ++i) {
        cin>>a[i];
        a[i+n] = a[i];
    }
    for(int i = 1; i <= 2 * n; ++i) {
        sum[i] = sum[i - 1] + a[i];
    }
    vector<vector<int>> f(2 * n + 1, vector<int> (2 * n + 1, INF));
    for(int i = 1; i <= 2* n; ++i) f[i][i] = 0;
    for(int tt = 0; tt < n; ++tt) {
        for(int len = 2; len <= n + tt; ++len) {
            for(int i = 1; i + len - 1 <= n + tt; ++i) {
                int j = i + len - 1;
                for(int k = i; k < j; ++k) {
                    f[i][j] = min(f[i][j], f[i][k] + f[k+1][j] + sum[j] - sum[i-1]);
                }
            }
        }
    }
    int mi = INF;
    for(int i = 1; i <= n; ++i) {
        mi = min(mi, f[i][i+n - 1]);
    }
    cout<<mi<<endl;
    f.assign(2 * n + 1, vector<int>(2*n+1, -INF));
    for(int i = 1; i <= 2*n; ++i) f[i][i] = 0;
    for(int tt = 0; tt < n; ++tt) {
        for(int len = 2; len <= n + tt; ++len) {
            for(int i = 1; i + len - 1 <= n + tt; ++i) {
                int j = i + len - 1;
                for(int k = i; k < j; ++k) {
                    f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + sum[j] - sum[i-1]);
                }
            }
        }
    }
    int ma = -INF;
    for(int i = 1; i <= n; ++i) {
        ma = max(ma, f[i][i + n - 1]);
    }
    cout<<ma;
}

总结

内部循环应该是从阶段到状态到决策。
阶段 → 状态 → 决策 阶段 \rightarrow 状态 \rightarrow 决策 阶段状态决策
对环的处理:

  • 断环为链 – 将两个进行拼接。在更新时要将2*n个元素都更新,而不能只更新到前n个,否则访问到n+1~2n时会出错。
  • 取模。

在环形问题中,可以选择(i+1)%n的方式,但也可以将n个元素复制一遍,变成2*n个元素,简化代码。

能量项链

问题描述:

代码:

void solve() {
    int n; cin>>n;
    vector<LL> a(n*2 + 1);
    for(int i = 1; i <= n; ++i) cin>>a[i], a[i + n] = a[i];
    vector<vector<LL>> f(2*n+1, vector<LL>(2*n+1));
    for(int len = 2; len <= n + 1; ++len) {
        for(int i = 1; i + len - 1 <= n * 2; ++i) {
            int j = i + len - 1;
            for(int k = i+1; k < j; ++k) {
                f[i][j] = max(f[i][j], f[i][k] + f[k][j] + a[i] * a[k] * a[j]);
            }
        }
    }
    LL ma = -INF;
    for(int i = 1; i <= n; ++i) {
        ma = max(ma, f[i][i + n]);
    }
    cout<<ma;
}
最大括号匹配数

问题描述:略。

转移方程:
$$
F(i,j) = \begin{cases}
if \quad s[i] 和 s[j] 是合法的一对儿括号 \quad F(i,j) = F(i+1,j-1) + 2 \
max{\sum_{i \leq k \leq j-1}F(i,k) + F(k+1,j)}

\end{cases}
$$
状态描述:

​ 在区间[i, j]中合法序列的最多个数。

边界:无。

目标:

F(1, N)

代码:

void solve() {
char ph[N];
	while(cin>>ph + 1) {
		if(ph[1] == '0') break;
		int n = strlen(ph + 1);
		vector<vector<int>> f(n+1, vector<int>(n+1));
		for(int len = 2; len <= n; ++len) {
			for(int i = 1;  i + len - 1 <= n; ++i) {
				int j = i + len - 1;
				if(ph[i] == '(' && ph[j] == ')' || ph[i] == '[' && ph[j] == ']') f[i][j] = f[i+1][j-1] + 2;
				for(int k = i; k < j; ++k) {
					f[i][j] = max(f[i][j], f[i][k] + f[k+1][j]);
				}
			}
		}
		cout<<f[1][n]<<endl;
	}
}
[P1435 IOI2000] 回文字串 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

问题描述:略。

最长公共子序列。

代码:

void solve() {
	string s1,s2; cin>>s1;
	int n = s1.size();
	s2 = s1;
	reverse(all(s2));
	s2 = " " + s2;
	s1 = " " + s1;
	vector<vector<int>> f(n+1, vector<int>(n+1));
	for(int i = 1; i <= n; ++i) {
		for(int j = 1; j <= n; ++j) {
			if(s1[i] == s2[j]) f[i][j] = f[i-1][j-1] + 1;
			else f[i][j] = max(f[i-1][j], f[i][j-1]);
		}
	}
	cout<<n - f[n][n];
}

最长公共子序列是按位向后比对的,所以a序列每个元素在b序列中的位置如果递增,就说明b中的这个数在a中的这个数整体位置偏后

换句话说,只要这个子序列在B中单调递增,它就是A的子序列。 — P1439 【模板】最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

O(n * log(n))的LCS

顺序映射,用O(n * log(n))的LIS模板即可。

void solve() {
	int n; cin>>n;
	vector<int> b(n);
	for(int i = 0; i < n; ++i) {
		int t; cin>>t;
		b[t-1] = i; 
	}
	vector<int> f;
	for(int i = 0; i < n; ++i) {
		int a; cin>>a;
		auto pos = lower_bound(all(f), b[a-1]);
		if(pos == f.end()) f.push_back(b[a-1]);
		else *pos = b[a-1];
	}
	cout<<f.size();
}
[P4342 IOI1998] Polygon - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

问题描述:一个多边形,点权为数,边权为操作符。删除一个边后,经相邻计算后(类似石子合并),最大值是多少。

转移方程:
KaTeX parse error: Got group of unknown type: 'internal'
状态描述:

F(i,j,k),如果k = 1,表示在区间i到j之间合并的最小值,否则k = 0,表示在区间i到j之间合并的最大值。

边界:
F ( i , j , k ) = { − i n f i f k = 0 & & i ! = j i n f i f k = 1 & & i ! = j v a l ( i , j ) i f i = = j F(i,j,k) = \begin{cases} -inf \quad if \quad k = 0 \&\& \quad i != j \\ inf \quad if \quad k = 1 \&\& \quad i != j \\ val(i,j) \quad if \quad i == j \end{cases} F(i,j,k)= infifk=0&&i!=jinfifk=1&&i!=jval(i,j)ifi==j
目标:
m a x 1 ≤ i ≤ ≤ N F ( i , i + N − 1 , 0 ) max_{1 \leq i \leq \leq N}F(i, i + N - 1,0) max1i≤≤NF(i,i+N1,0)
代码:

void solve() {
	int n; cin>>n;
	vector<int> a(2 * n + 1);
	vector<int> op(2 * n + 1);
	for(int i = 1; i <= n; ++i) {
		char opt; int val;
		cin>>opt>>val;
		if(opt == 't') op[i-1] = 1; // 1 +
		else op[i-1] = 2; // 2 *
		a[i] = val;
		a[n + i] = a[i];
		op[i - 1 + n] = op[i - 1];
	}
	vector<vector<vector<int>>> f(2 * n + 1, vector<vector<int>>(2 * n + 1, vector<int>(2)));
	for(int i = 1; i <= 2 * n; ++i) f[i][i][0] = f[i][i][1] = a[i];
	for(int i = 1; i <= 2 * n; ++i) {
		for(int j = 1; j <= 2 * n; ++j) {
			if(i == j) continue;
			f[i][j][1] = INF;
			f[i][j][0] = -INF;
		}
	}
	for(int len = 2; len <= n; ++len) {
		for(int i = 1; i + len -1 <= 2 * n; ++i) {
			int j = i + len - 1;
			for(int k = i; k < j; ++k) {
				if(op[k] == 1) {
					// +
					f[i][j][0] = max(f[i][j][0], f[i][k][0] + f[k+1][j][0]);
					f[i][j][0] = max(f[i][j][0], f[i][k][1] + f[k+1][j][1]);
					f[i][j][1] = min(f[i][j][1], f[i][k][1] + f[k+1][j][1]);
					f[i][j][1] = min(f[i][j][1], f[i][k][0] + f[k+1][j][0]);
				} else {
					// *
					f[i][j][0] = max(f[i][j][0], f[i][k][0] * f[k+1][j][0]);
					f[i][j][0] = max(f[i][j][0], f[i][k][1] * f[k+1][j][1]);
					f[i][j][1] = min(f[i][j][1], f[i][k][1] * f[k+1][j][1]);
					f[i][j][1] = min(f[i][j][1], f[i][k][0] * f[k+1][j][0]);					
				}
			}
		}
	}
	int ma = -INF;
	vector<int> ans;
	for(int i = 1; i <= n; ++i) {
		ma = max(ma, f[i][i+n-1][0]);
	}
	for(int i = 1; i <= n; ++i) {
		if(ma == f[i][i+n-1][0]) {
			ans.push_back(i);
		}
	}

	cout<<ma<<endl;
	for(auto t: ans) cout<<t<<" ";
}

总结

  • 注意乘法运算性质。两个负数相乘是整数。可能现在是两块较小的数,但是经过一次操作就成特别大的数。
  • 石子合并类似
凸多边形的划分 (nowcoder.com)

转移方程:
F ( i , j ) = m i n i + 1 ≤ k ≤ j − 1 ( F ( i , j ) , F ( i , k ) + F ( k , j ) + A [ i ] ∗ A [ j ] ∗ A [ k ] ) F(i,j) = min_{i + 1\leq k \leq j-1}(F(i,j), F(i,k) + F(k,j) + A[i] * A[j] * A[k]) F(i,j)=mini+1kj1(F(i,j),F(i,k)+F(k,j)+A[i]A[j]A[k])
状态表示:

F(i,j)表示区间i到j的最小价值

边界:
F ( i , i ) = A [ i ] F ( i , i + 1 ) = 0 1 ≤ i ≤ 2 ∗ N F(i,i) = A[i] \\ F(i,i+1) = 0 \quad 1 \leq i \leq 2 * N F(i,i)=A[i]F(i,i+1)=01i2N
目标:
F ( i , i + N − 1 ) 1 ≤ i ≤ N F(i, i + N - 1) \quad 1 \leq i \leq N F(i,i+N1)1iN
代码:

import math # 有高精度(
f = [[math.inf] * 154 for _ in range(154)]
n = int(input())
a = [0 for _ in range(2 * n + 12)]

arr = list(map(int, input().split()))

for i in range(1, n + 1):
    a[i] = arr[i-1]
    a[i + n] = a[i]

for i in range(1, 2 * n + 1):
    f[i][i] = a[i]
    f[i][i + 1] = 0

for len in range(2, n + 1):
    for i in range(1, 2 * n - len + 1 + 1):
        j = i + len - 1
        for k in range(i+1,j):
            f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k])

mi = math.inf
for i in range(1,n + 1):
    mi = min(mi, f[i][i + n - 1])
print(mi)

总结:与能量项链相同,只不过是求最小值。

P1220 关路灯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

转移方程:
F ( i , j , 0 ) = m i n ( F ( i , j , 0 ) , F ( i + 1 , j , 0 ) + c o s t 1 , F ( i + 1 , j , 1 ) + c o s t 2 ) F ( i , j , 1 ) = m i n ( F ( i , j , 1 ) , F ( i , j − 1 , 0 ) + c o s t 1 , F ( i , j − 1 , 1 ) + c o s t 1 ) F(i,j,0) = min(F(i,j,0), F(i+1,j,0) + cost1, F(i+1,j,1) + cost2) \\ F(i,j,1) = min(F(i,j,1), F(i,j-1,0) + cost1, F(i,j-1,1) + cost1) F(i,j,0)=min(F(i,j,0),F(i+1,j,0)+cost1,F(i+1,j,1)+cost2)F(i,j,1)=min(F(i,j,1),F(i,j1,0)+cost1,F(i,j1,1)+cost1)
状态表示:

F(i,j,k),当k = 1时,表示移到i段最小价值,当k = 0时,表示移到j段最小价值。

边界:
F ( i , i , 0 ) = F ( i , i , 1 ) i 为初始位置 F(i,i,0) = F(i,i,1) \quad i为初始位置 F(i,i,0)=F(i,i,1)i为初始位置
目标:
F ( 1 , N ) F(1,N) F(1,N)
代码:文章来源地址https://www.toymoban.com/news/detail-638337.html

void solve() {
    int n; cin>>n; int lz; cin>>lz;
    vector<LL> sit(n + 2), gl(n + 2), sum(n + 2);
    vector<vector<vector<LL>>> f(n + 2, vector<vector<LL>>(n + 2, vector<LL> (2, INF)));
    for(int i = 1; i <= n; ++i) cin>>sit[i]>>gl[i], sum[i] = sum[i-1] + gl[i];
    f[lz][lz][1] = f[lz][lz][0] = 0;
    auto cal = [&] (int l, int r) -> LL {
        return sum[n] - (sum[r] - sum[l-1]);
    };
    for(int len = 2; len <= n; ++len) {
        for(int i = 1; i + len - 1 <= n; ++i) {
            int j = i + len - 1;
            for(int k = i; k < j; ++k) { // 好像没有用上

                f[i][j][0] = min(f[i][j][0], f[i+1][j][0] + (sit[i+1] - sit[i]) * cal(i+1,j));
                f[i][j][0] = min(f[i][j][0], f[i+1][j][1] + (sit[j] - sit[i]) * cal(i+1,j));

                f[i][j][1] = min(f[i][j][1], f[i][j-1][0] + (sit[j] - sit[i]) * cal(i, j-1));
                f[i][j][1] = min(f[i][j][1], f[i][j-1][1] + (sit[j] - sit[j-1]) * cal(i,j-1));
            }
        }
    }
    cout<<min(f[1][n][0], f[1][n][1]);
}

到了这里,关于区间dp 解题报告的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++---区间DP---加分二叉树(每日一道算法2023.4.28)

    题目: 设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。 每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下: subtree的左

    2024年02月06日
    浏览(25)
  • 【区间 DP】运用区间 DP 解决古老原题

    这是 LeetCode 上的 「664. 奇怪的打印机」 ,难度为 「困难」 。 Tag : 「区间 DP」 有台奇怪的打印机有以下两个特殊要求: 打印机每次只能打印由 同一个字符 组成的序列。 每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符。 给你一个字符串 s ,你的

    2024年02月07日
    浏览(28)
  • DP(区间DP)

    目录 石子合并   合并果子(贪心 Huffman树) 环形石子合并 设有 N 堆石子排成一排,其编号为 1,2,3,…,N。 每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。 每次只能 合并相邻 的两堆,合并的代价为这两堆石子的质量之和,合并后与这

    2024年02月13日
    浏览(25)
  • 石子合并(区间dp)

    (1)f[l][r]表示l~r之间合并的最小代价。 (2)将l~r拆成l~k,k+1~r两份分别最优合并,再把两份合并,对于每个l,r通过枚举所有可能k探寻最优。 (3)最终目标是f[1][n],注意到长区间是由短区间递推出来的,所以由小到大枚举区间长度,再枚举起点,此时l = 起点,r = 起点 +len

    2024年02月10日
    浏览(26)
  • 区间dp(动态规划)

    动态规划(dp)是一种通过将问题分解为子问题,并利用已解决的子问题的解来求解原问题的方法。适用于具有重叠子问题和最优子结构性质的优化问题。通过定义状态和状态转移方程,动态规划可以在避免重复计算的同时找到问题的最优解,是一种高效的求解方法,常用于

    2024年02月15日
    浏览(30)
  • 动态规划——区间DP 学习笔记

    不含四边形不等式优化。 线性动态规划的局限性在于,它只能顺推或倒退,而不能有子区间依赖的问题。 区间动态规划是线性动态规划的扩展,它将问题划分为若干个子区间,并通过定义状态和状态转移方程来求解每个子区间的最优解,最终得到整个区间的最优解。 区间动

    2024年02月08日
    浏览(33)
  • 动态规划——区间dp [石子合并]

    动态规划(dp)是一种通过将问题分解为子问题,并利用已解决的子问题的解来求解原问题的方法。适用于具有重叠子问题和最优子结构性质的优化问题。通过定义状态和状态转移方程,动态规划可以在避免重复计算的同时找到问题的最优解,是一种高效的求解方法,常用于

    2024年02月12日
    浏览(38)
  • 动态规划系列 | 一文搞定区间DP

    区间 DP 可以用于解决一些涉及到区间合并或分割的问题。区间 DP 通常有以下三个特点: 合并(分割) :将两个或多个部分进行整合,或者反过来将一个区间分解成多个部分。 特征 :能将问题分解为能两两合并的形式。 求解 :对整个问题设最优解,枚举合并点,将问题分

    2024年02月02日
    浏览(35)
  • 蓝桥杯真题:密码脱落(区间dp)

    目录 题目: 解题思路: dp分析:  解题代码: 题目要求的为脱落的种子数(即回文字符中没有对应回文的字符的数量) 我们可以转换成求回文字符串最长回文字符串的长度

    2024年02月16日
    浏览(26)
  • 石子合并(动态规划 区间DP)+详细注释

    原题链接   活动 - AcWing 题目 设有 N 堆石子排成一排,其编号为 1,2,3,…,N。 每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相

    2024年02月16日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包