《算法进阶指南》--单调队列优化DP-围栏-后效性的处理
f[i][j]表示前i块木板考虑前j个工匠的最大收益,f[i][j]表示的所有方案可分为三种,1.工匠j不涂,可用f[i][j-1]表示 2.工匠j涂了,但不涂第i块木板,可用f[i-1][j]表示 3.工匠j涂了第i块木板,包括j所有可取的左端点
后效性处理:在转移时,工匠应该以s大小排序,如果s小的工匠在s大的工匠后转移,这是由于s小的工匠一定比s大的工匠先涂,逆序会使s大的工匠转移时无法包括s小的工匠的木板状态,那么排序后为什么不会使s小的工匠无法包括s大的工匠的状态?按上面的描述进行转移,我们会发现第j个工匠只会从s之前转移到s之后,工匠的粉刷区间不重叠,那么s更大的工匠的粉刷的木板s之前的所有情况都一定被包含在了集合之内。
#include <bits/stdc++.h>
#define int long long
#define INF 1e18
#define endl '\n'
#define MOD (int)(998244353)
using namespace std;
struct Node
{
int l;
int pri;
int ms;
const bool operator<(Node &a) const
{
return this->ms < a.ms;
}
};
int n, m;
Node can[20000];
int dp[20000][102];
void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> can[i].l >> can[i].pri >> can[i].ms;
}
sort(can + 1, can + m + 1);
vector<vector<int>> maxarr(m + 5, vector<int>(n + 5, -1));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
int nowl = can[j].l, nows = can[j].ms, nowp = can[j].pri;
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if (nows > i || (i - nows + 1) > nowl)
{
continue;
}
if (nows == i)
{
for (int k = 1; k <= nowl; k++)
{
int lindex = i - k;
lindex = max((int)0, lindex);
maxarr[j][lindex] = max(maxarr[j][lindex + 1], dp[lindex][j - 1] + (can[j].pri) * (nows - lindex));
}
}
int lindex = i - nowl;
lindex = max((int)0, lindex);
dp[i][j] = max(dp[i][j], maxarr[j][lindex] + can[j].pri * (i - nows));
}
}
cout << dp[n][m] << endl;
}
signed main()
{
// std::ios::sync_with_stdio(0);
// std::cin.tie(0);
int T = 1;
// cin >> T;
for (int i = 1; i <= T; i++)
{
solve();
}
return 0;
}
收获:
后效性处理:从状态表示的集合入手, 情况2.工匠j涂了,但不涂第i块木板的集合,无法包括工匠j之前的工匠涂了第i块木板的情况,但是排序后不会出现“工匠j涂了且工匠j之前的工匠涂了第i块木板”的情况

浙公网安备 33010602011771号