【Luogu】动态规划七
P1566 加等式 - 洛谷
思路:
其实这道题就是一个纸老虎,说这么多,其实最后就是问所有 a[i] 的组成方法之和有多少种
那么显然的一个dp就是 dp[j] += dp[j - a[i]]
然后这题就结束了,就是这么简单,最后记得减去 n,因为 a[i] 可由自己组成,但是题目说了必须要由其他数组成
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include <iomanip>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <utility>
#include <array>
#include <tuple>
using namespace std;
#define int long long
#define yes cout << "YES" << endl
#define no cout << "NO" << endlvoid solve()
{int n;cin >> n;vector<int> dp(30005,0),a(n);for (int i = 0; i < n; i++){cin >> a[i];}dp[0] = 1;for (int i = 0; i < n; i++){for (int j = 30000; j >= a[i]; j--){dp[j] += dp[j - a[i]];}}int res = -n;for (int i = 0; i < n; i++){res += dp[a[i]];}cout << res << endl;
}
signed main()
{cin.tie(0)->sync_with_stdio(false);int t = 1;cin >> t;while (t--){solve();}return 0;
}
P3133 [USACO16JAN] Radio Contact G - 洛谷
思路:
这题有两个人,根据问什么设什么,我们能简单想到一个dp,就是 dp[i][j] 其代表的含义就是前一个人走了 i 步,后一个人走了 j 步的最小花费
那么这个就有四种转移,分别是
①.第一个人不走,第二个人走
②.第一个人走,第二个人不走
③.两个人都走
④.两个人都不走
显然④不可能出现,所以只需要转移前三种情况了
那么转移就是 dp[i][j] = min(1,2,3) + dis(i,j),其中 dis(i,j) 是第一个人走了i步后的坐标与第二个人走了j步后的坐标的举例
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include <iomanip>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <utility>
#include <array>
#include <tuple>
using namespace std;
#define int long long
#define yes cout << "YES" << endl
#define no cout << "NO" << endl
int dp[1005][1005];
pair<int, int> f[1005], b[1005];
map<char, pair<int, int>> dir;int dis(int u,int v)
{return pow(f[u].first - b[v].first, 2) + pow(f[u].second - b[v].second, 2);
}void solve()
{int n, m;cin >> n >> m;cin >> f[0].first >> f[0].second;cin >> b[0].first >> b[0].second;dir['N'] = {0,1}, dir['E'] = {1,0}, dir['S'] = {0,-1}, dir['W'] = {-1,0};for (int i = 1; i <= n; i++){char c;cin >> c;f[i].first = f[i - 1].first + dir[c].first;f[i].second = f[i - 1].second + dir[c].second;}for (int i = 1; i <= m; i++){char c;cin >> c;b[i].first = b[i - 1].first + dir[c].first;b[i].second = b[i - 1].second + dir[c].second;}for (int i = 1; i <= n; i++) dp[i][0] = dp[i - 1][0] + dis(i, 0);for (int i = 1; i <= m; i++)dp[0][i] = dp[0][i - 1] + dis(0, i);for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + dis(i, j);}}cout << dp[n][m];
}
signed main()
{//cin.tie(0)->sync_with_stdio(false);int t = 1;//cin >> t;while (t--){solve();}return 0;
}
P2734 [USACO3.3] 游戏 A Game - 洛谷
思路:
区间dp,很有意思
题目让我们求取的最大值,这道题显然可以用dp
我们定义 dp[i][j] 为 i ~ j 区间内先手的人能取得的最大值
那么转移也就很明显了,由于每次我们只能取两边的数,所以对于 dp[i][j] 就可以从 dp[i+1][j] 或 dp[i][j-1] 转移来,那么具体方程怎么写呢?
其实就是 dp[i][j] = max(s - dp[i+1][j],s - dp[i][j-1]) 其中 s 是区间 i~j 的和
那为什么是这样转移呢?我们假设当前是第一个人选,那么之前就是第二个人选,那第二个人也是按最优的选法选的,那么他的值就是 dp[i+1][j] 或 dp[i][j-1] 中的较小值(毕竟是后手),所以我们取max即可
注意初始化以及枚举顺序,这里我们枚举大到小枚举左端点,然后从小到大枚举右端点
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include <iomanip>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <utility>
#include <array>
#include <tuple>
using namespace std;
#define int long long
#define yes cout << "YES" << endl
#define no cout << "NO" << endl
int f[105][105];
void solve()
{int n;cin >> n;vector<int> a(n+1),sum(n+1);for (int i = 1; i <= n; i++){cin >> a[i];sum[i] = sum[i - 1] + a[i];f[i][i] = a[i];}for (int i = n - 1; i >= 1; i--){for (int j = i + 1; j <= n; j++){int s = sum[j] - sum[i - 1];f[i][j] = max(s - f[i + 1][j],s- f[i][j - 1]);}}cout << f[1][n] << " " << sum[n] - f[1][n];
}
signed main()
{//cin.tie(0)->sync_with_stdio(false);int t = 1;//cin >> t;while (t--){solve();}return 0;
}
P3004 [USACO10DEC] Treasure Chest S - 洛谷
思路:
上面这题的双倍经验,思路一模一样,但是要优化空间复杂度
我们发现 dp[i][j] 的转移只与当前区间长度减一的区间有关,所以我们可以定义 dp[i] 为 以 i 为起点区间长度为 len 时的最大值,然后枚举从小到大枚举区间长度,从小到大枚举左端点即可
代码:
#include <iostream>
#include <algorithm>
#include<cstring>
#include <iomanip>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <utility>
#include <array>
#include <tuple>
using namespace std;
#define int long long
#define yes cout << "YES" << endl
#define no cout << "NO" << endl
int f[5001];
int sum[5001],a[5001];
void solve()
{int n;cin >> n;for (int i = 1; i <= n; i++){cin >> a[i];sum[i] = sum[i - 1] + a[i];}for (int len = 1; len <= n; len++) {for (int i = 1; i + len - 1 <= n; i++) { // i 是区间的起始位置int j = i + len - 1; // 结束位置int s = sum[j] - sum[i - 1]; // 当前区间的和if (len == 1) {f[i] = a[i]; // 只有一个元素的情况}else {f[i] = max(s - f[i + 1], s - f[i]); // 状态转移}}}cout << f[1];
}
signed main()
{//cin.tie(0)->sync_with_stdio(false);int t = 1;//cin >> t;while (t--){solve();}return 0;
}