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;
}

posted @ 2025-09-22 23:02  Water_M  阅读(12)  评论(0)    收藏  举报