9.20 模拟赛 T4
题意:有 \(n\) 个点,每个点有四个属性 \(a_i,b_i,c_i,v_i\)。现在要在这 \(n\) 个点之间连无向边。要求:
- \(i\) 的度数至少为 \(a_i\)。
- \(i\) 的所有邻居 \(j\) 的 \(v_j\),要么都小于等于 \(v_i\),要么都大于等于 \(v_i\)。
当小于等于的时候,\(i\) 这个点的收益为 \(b_i\);大于等于的时候,收益为 \(c_i\)。如果邻居的 \(v_j\) 值全部等于 \(v_i\),那么收益为 \(\max(b_i,c_i)\)。最大化收益,或报告无解。
\(1 \le n \le 400,0 \le a_i \le n, 1 \le v_i \le n, 0 \le b_i,c_i \le 10^9\)。
特殊性质:所有 \(v_i\) 互不相同的情况
称选了 \(b_i\) 的点为【前向】,选了 \(c_i\) 的点为【后向】。
(即使所有邻居的 \(v\) 值相同,也要分成这两类)
按照所有\(v_i\) 递增的顺序排序。不难想到分组问题的一般设置状态方法,设 \(f_{i, j}\) 为考虑前 \(i\) 个人,考虑完第 \(i\) 个人之后有 \(j\) 个人是【后向】的。发现转移的时候要考虑,这 \(j\) 个人中,还需要安排多少个入度。
这里有一个性质,如果一个图是合法的,那么不改变点的【前后向性】的前提下,给它多加几条边是一定不会更劣的。
更改状态为,\(f_{i,j,k}\) 表示,\(i,j\) 同前,这 \(j\) 个人中,最大还需要安排 \(k\) 个入度。转移就考虑 \(f_{i,j,k}\) 转移到哪。
-
\(i+1\) 是【前向】:
那么必然会和 \(j\) 个【后向】点产生连边。首先,如果 \(a_i > j\),那么转移就是不合法的。然后,由上面的性质,多连边不劣,我可以给每个【后向】点都,消掉一些 \(a_x\) 的需求。就是,让前面还需要连的边更少一点。
所以 \(f_{i,j,k} + b_{i+1} \to f_{i+1,j,\max(k-1,0)}\)。 -
\(i+1\) 是【后向】:
这样 \(j\) 就多一。然后顺便更新一下【最大需求入度】\(k\)。
\(f_{i,j,k}+c_{i+1} \to f_{i+1,j+1,\max(k,a_{i+1})}\)。
答案就是 \(\min_j f_{n,j,0}\)。
做到 \(O(n^3)\)。
实现上大概是这样:
memset(f, -63, sizeof(f));
f[0][0] = 0;
upw(i, 0, n-1) {
memset(g, -63, sizeof(f));
upw(j, 0, n) upw(k, 0, n) {
if(a[i+1] <= j) vmax(g[j][max(k-1, 0ll)], f[j][k] + b[i+1]);
vmax(g[j+1][max(k, a[i+1])], f[j][k] + c[i+1]);
}
memcpy(f, g, sizeof(f));
}
int ans = -Linf;
upw(j, 0, n) vmax(ans, f[j][0]);
if(ans < -1e18) cout << "explode\n";
else cout << ans << '\n';
空间开不下 long long 的 \(O(n^3)\) 数组。故滚动数组消掉一维。
推广这个做法
发现推广的困难之处在于,\(v_i\) 相等的一些点,无法钦定一个确定的转移顺序。总会有一些连边会算多或算少。
然后考虑对于所有 \(v_i\) 相等的块,统一转移。
思路是,对于每一个块,首先可以对块内点两两连边不劣,再枚举多少个点【后向】,多少个点【前向】,发现转移只需要关心【后向】点整体最【紧】的限制,即所有 \(a_i\) 的最大值,以及【前向】点的 \(a_i\) 的最大值。
预处理一个 \(pre_{i,j,mx1,mx2}\),表示 \(v\) 值为 \(i\) 的所有点中,【后向】点有 \(j\) 个,其中所有【后向点】的 \(\max a_i\) 是 \(mx1\),所有【前向】点的 \(\max a_i\) 是 \(mx2\)。
这样空间太大,吃不消。
发现 \(mx1\) 和 \(mx2\) 中,必有一个是整块的全局 \(\max\)。
更换状态为 \(pre_{i,j,mx,0/1}\),其中 \(i,j\) 意义不变,\(0\) 表示 \(mx\) 记录的是【前向】点的 \(\max\),而 \(1\) 表示记录的是【后向】点的 \(\max\)。
转移只需要用一个临时数组 \(g\) 记录。
这里 \(pre\) 中的 \(mx\),对转移可能造成【实际情况,后向点的 \(\max\) 是 \(mx=\max a_i\),但此时前向点的 \(\max\) 并不是 \(\max a_i\),然而用的时候当成它来用了】的情况,你发现这样记录一定不优,会在对立面被记到。
实现大概就是,先离散化一下,记录每一个 \(v\) 值的点集,再转移。
vector<int> vx{-1};
upw(i, 1, n) vx.push_back(v[i]);
sort(all(vx)), vx.erase(unique(all(vx)), vx.end());
upw(i, 1, n) v[i] = lower_bound(all(vx), v[i]) - vx.begin();
mxv = (int)vx.size() - 1;
vector<vector<int> > pos(mxv+1);
upw(i, 1, n) pos[v[i]].push_back(i);
upw(i, 1, n) a[i] = max(0ll, a[i] - (int)pos[v[i]].size() + 1);
upw(i, 1, mxv) sort(all(pos[i]), [&] (int i, int j) { return a[i] < a[j]; });
vector<vector<vector<vector<int> > > > pre(mxv + 1);
upw(val, 1, mxv) {
int cnt = pos[val].size();
pre[val].resize(cnt + 1, vector<vector<int> >(n+1, vector<int>(2)));
vector<vector<int> > g(cnt+1, vector<int>(n+1, -inf));
g[0][0] = 0;
for(auto i : pos[val]) {
dnw(j, cnt-1, 0) {
upw(k, 0, n) {
//后向
vmax(g[j+1][max(k, a[i])], g[j][k] + c[i]);
//前向
g[j][k] += b[i];
}
}
}
upw(j, 0, cnt) upw(k, 0, n)
pre[val][j][k][1] = g[j][k];
g = vector<vector<int> >(cnt+1, vector<int>(n+1, -inf));
g[0][0] = 0;
for(auto i : pos[val]) {
dnw(j, cnt-1, 0) {
upw(k, 0, n) {
vmax(g[j+1][max(k, a[i])], g[j][k] + b[i]);
g[j][k] += c[i];
}
}
}
upw(j, 0, cnt) upw(k, 0, n)
pre[val][cnt - j][k][0] = g[j][k];
}
效仿特殊性质,就是自然的转移。
vector<vector<vector<int> > > f(n+1, vector<vector<int> >(n+1, vector<int>(n+1, -Linf)));
f[0][0][0] = 0;
upw(i, 0, mxv - 1) {
int mxa = 0, cnt = (int)pos[i+1].size();
for(auto j : pos[i+1]) vmax(mxa, a[j]);
upw(j, 0, n) upw(k, 0, n) {
upw(c, 0, cnt) upw(mx, 0, n) upw(o, 0, 1) {
int mxb, mxf;
if(o == 1) mxb = mx, mxf = mxa;
else mxb = mxa, mxf = mx;
if(mxf <= j && j+c <= n) vmax(f[i+1][j+c][max(k - (cnt - c), mxb)],
f[i][j][k] + pre[i+1][c][mx][o]);
}
}
}
int ans = -Linf;
upw(j, 0, n) vmax(ans, f[mxv][j][0]);
if(ans < -1e18) cout << "explode\n";
else cout << ans << '\n';
有五重循环,时间复杂度看似是 \(O(n^5)\) 的,但是仔细想想中间 \(j\) 的那一重,对于所有 \(i\),总和是 \(O(n)\) 的,故总复杂度 \(O(n^4)\)。
同理,\(pre\) 的预处理是 \(O(n^3)\) 的,不用进一步优化。
优化
先对于 \(o=0/1\) 拆开来写。
f[0][0] = 0;
upw(i, 0, mxv - 1) {
vector<vector<int> > g(n+1, vector<int>(n+1, -Linf));
int mxa = 0, cnt = (int)pos[i+1].size();
for(auto j : pos[i+1]) vmax(mxa, a[j]);
upw(j, 0, n) upw(k, 0, n) {
upw(c, 0, cnt) upw(mx, 0, n) {
if(mxa <= j && j+c <= n) vmax(g[j+c][max(k - (cnt - c), mx)], f[j][k] + pre[i+1][c][mx][1]);
if(mx <= j && j+c <= n) vmax(g[j+c][max(k - (cnt - c), mxa)], f[j][k] + pre[i+1][c][mx][0]);
}
}
f = g;
}
这里用滚动数组了。
先看下面那一个 if
里面的。发现对 \(g\) 特定位置的更新,不依赖 \(mx\) 的枚举。用前缀 \(\max\) 轻松去掉一层循环。
f[0][0] = 0;
upw(i, 0, mxv - 1) {
int mxa = 0, cnt = (int)pos[i+1].size();
vector<vector<int> > g(n+1, vector<int>(n+1, -Linf));
vector<vector<int> > premx0(cnt+1, vector<int>(n+1, -Linf));
upw(c, 0, cnt) upw(mx, 0, n)
premx0[c][mx] = max((mx >= 1 ? premx0[c][mx - 1] : -Linf), pre[i+1][c][mx][0]);
for(auto j : pos[i+1]) vmax(mxa, a[j]);
upw(j, 0, n) upw(k, 0, n) {
upw(c, 0, min(cnt, n-j)) upw(mx, 0, n) {
if(mxa <= j) vmax(g[j+c][max(k - (cnt - c), mx)], f[j][k] + pre[i+1][c][mx][1]);
// if(mx <= j) vmax(g[j+c][max(k - (cnt - c), mxa)], f[j][k] + pre[i+1][c][mx][0]);
}
upw(c, 0, min(cnt, n-j))
vmax(g[j+c][max(k - (cnt - c), mxa)], f[j][k] + premx0[c][j]);
}
f = g;
}
然后对于上面那一个 if
,发现里面 g[j+c][max(k - (cnt - c), mx)]
怎么还有一个 \(\max\)。对 \(mx \ge k - (cnt - c)\) 是否满足分类讨论:
- \(mx < k - (cnt - c)\):于是又类似上面的,\(g\) 的更新不依赖 \(mx\) 的枚举,前缀 \(\max\) 优化掉。
- \(mx \ge k - (cnt - c)\):这回,\(g\) 的更新不依赖 \(k\) 的枚举,用前缀 \(\max\) 记录 \(f\) 即可。
至此,做到 \(O(n^3)\)。空间复杂度 \(O(n^2)\)。动态申请空间大常数,但刚好跑得进 3s。
#include <bits/stdc++.h>
using namespace std;
#define filename "fly"
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
#define multi_cases 1
#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int>
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)
template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }
const int N = 402;
#define int long long
int n, a[N], b[N], c[N], v[N];
int mxv;
void WaterM() {
cin >> n;
upw(i, 1, n) cin >> a[i] >> b[i] >> c[i] >> v[i];
vector<int> vx{-1};
upw(i, 1, n) vx.push_back(v[i]);
sort(all(vx)), vx.erase(unique(all(vx)), vx.end());
upw(i, 1, n) v[i] = lower_bound(all(vx), v[i]) - vx.begin();
mxv = (int)vx.size() - 1;
vector<vector<int> > pos(mxv+1);
upw(i, 1, n) pos[v[i]].push_back(i);
upw(i, 1, n) a[i] = max(0ll, a[i] - (int)pos[v[i]].size() + 1);
upw(i, 1, mxv) sort(all(pos[i]), [&] (int i, int j) { return a[i] < a[j]; });
vector<vector<vector<vector<int> > > > pre(mxv + 1);
upw(val, 1, mxv) {
int cnt = pos[val].size();
pre[val].resize(cnt + 1, vector<vector<int> >(n+1, vector<int>(2)));
vector<vector<int> > g(cnt+1, vector<int>(n+1, -inf));
g[0][0] = 0;
for(auto i : pos[val]) {
dnw(j, cnt-1, 0) {
upw(k, 0, n) {
//后向
vmax(g[j+1][max(k, a[i])], g[j][k] + c[i]);
//前向
g[j][k] += b[i];
}
}
}
upw(j, 0, cnt) upw(k, 0, n)
pre[val][j][k][1] = g[j][k];
g = vector<vector<int> >(cnt+1, vector<int>(n+1, -inf));
g[0][0] = 0;
for(auto i : pos[val]) {
dnw(j, cnt-1, 0) {
upw(k, 0, n) {
vmax(g[j+1][max(k, a[i])], g[j][k] + b[i]);
g[j][k] += c[i];
}
}
}
upw(j, 0, cnt) upw(k, 0, n)
pre[val][cnt - j][k][0] = g[j][k];
}
vector<vector<int> > f(n+1, vector<int>(n+1, -Linf));
f[0][0] = 0;
upw(i, 0, mxv - 1) {
int mxa = 0, cnt = (int)pos[i+1].size();
vector<vector<int> > g(n+1, vector<int>(n+1, -Linf));
vector<vector<int> > premx0(cnt+1, vector<int>(n+1, -Linf));
vector<vector<int> > premx1(cnt+1, vector<int>(n+1, -Linf));
upw(c, 0, cnt) upw(mx, 0, n) {
premx0[c][mx] = max((mx >= 1 ? premx0[c][mx - 1] : -Linf), pre[i+1][c][mx][0]);
premx1[c][mx] = max((mx >= 1 ? premx1[c][mx - 1] : -Linf), pre[i+1][c][mx][1]);
}
for(auto j : pos[i+1]) vmax(mxa, a[j]);
vector<vector<int> > mxf(n+1, vector<int>(n+1, -Linf));
upw(j, mxa, n) upw(k, 0, n)
mxf[j][k] = max((k >= 1 ? mxf[j][k-1] : -Linf), f[j][k]);
upw(j, mxa, n)
upw(c, 0, min(cnt, n-j))
upw(mx, 0, n) {
int bound = min(mx + cnt - c, n);
// upw(k, 0, min(mx + cnt - c, n))
// vmax(g[j+c][mx], f[j][k] + pre[i+1][c][mx][1]);
vmax(g[j+c][mx], mxf[j][bound] + pre[i+1][c][mx][1]);
}
upw(j, mxa, n) upw(k, 0, n) {
upw(c, 0, min(cnt, n-j)) {
int bound = max(0ll, k - (cnt - c)) - 1;
if(bound >= 0) vmax(g[j+c][k - (cnt - c)], f[j][k] + premx1[c][bound]);
}
}
upw(j, 0, n) upw(k, 0, n) {
upw(c, 0, min(cnt, n-j))
vmax(g[j+c][max(k - (cnt - c), mxa)], f[j][k] + premx0[c][j]);
}
f = g;
}
int ans = -Linf;
upw(j, 0, n) vmax(ans, f[j][0]);
if(ans < -1e18) cout << "explode\n";
else cout << ans << '\n';
}
signed main() {
#ifdef filename
FileOperations();
#endif
signed _ = 1, Case;
#ifdef multi_cases
scanf("%d%d", &Case, &_);
#endif
while(_--) WaterM();
return 0;
}