11.9 模拟赛 T3

题意:将 \(n\) 个线段分成恰好 \(m\) 组,每个线段需要且只能分进一组。求这 \(m\) 组线段合法的得分之和最大是多少。一组线段的得分定义为它们的交的长度(区间长度为右端点减左端点)。一个方案合法,当且仅当每组线段有交且长度大于 \(0\)。保证有解。
\(m \le n \le 200\)。线段端点取值 \([0, 10^5]\)

Hint

考虑线段两两不包含怎么做。

Answer:显然按照左端点(或右端点)排序,而且显然存在一种最优方案使得每组都选择一个区间。\(O(n^3)\) DP 即可。

Solution

如果有一条线段 \(A\) 包含于另一条线段 \(B\),那么 \(B\) 可以认为是不重要的。因为他如果和 \(A\) 分在同一组,对该组得分一定没有贡献。

但是我们也不能直接删掉 \(B\)。原因是,直接删掉所有有“孩子线段”的线段,可能剩下的不足以分成 \(m\) 组。

同时,我们又不能直接将 \(B\) 单独成段计入贡献。原因类似。

那我们考虑拿出所有的 \(B\)。发现这些 \(B\) 之间可能还存在包含关系,然后发现这就是一个子问题
那我们需要的是,对 \(B\) 计算出分成 \(i \in [0, m]\) 组的最大贡献,对剩下来的部分,也求出来分成 \(i \in [0, m]\) 的最大贡献。背包背起来就是答案。这个过程可以递归进行。

但是有一个细节。\(B\) 集中的某条线段可能和某一个 \(A\) 同一组。这时 \(B\) 集中就不需要选该条线段从而使答案更大。
所以 DP 的时候额外记录一下该部分有没有线段不选即可。

输出答案的时候自然不能允许存在不选的线段。

时间复杂度 \(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;

//#define filename "intervals" 
#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 = 205;

int n, m, a[N], b[N];

int check(int i, vector<int> p) {
	for(auto j : p) if(i != j) {
		if(a[i] <= a[j] && b[j] <= b[i]) return 1;
	}
	return 0;
}

vector<vector<int> > solve(vector<int> &p) {
	if(p.empty()) return vector<vector<int> >(m+1, vector<int>(2));
	
	vector<int> c;
	for(auto it = p.begin(); it != p.end(); ) {
		if(check(*it, p)) c.push_back(*it), it = p.erase(it);
		else ++it;
	}
	auto g = solve(c);
	sort(all(p), [&] (int i, int j) { return a[i] < a[j]; });	//现在不存在区间互相包含了
	
	vector<vector<vector<int> > > f(p.size()+1, vector<vector<int> >(m+1, vector<int>(2, -inf)));
	f[0][0][0] = 0;	//去掉了0条线段
	
	upw(j, 1, m) {
		upw(i, 1, (int)p.size()) {
			int L = -inf, R = inf;
			dnw(i_, i-1, 0) {
				int y = p[i_];
				vmax(L, a[y]), vmin(R, b[y]);
				if(L >= R) break;
				vmax(f[i][j][0], f[i_][j-1][0] + (R - L));
				// cerr << "; " << i_ << '\n';
			}
		}
	}
	
	upw(i, 1, (int)p.size()) f[i][0][1] = 0;
	
	upw(j, 1, m) {
		// cerr << j << '\n';
		upw(i, 1, (int)p.size()) {
			vmax(f[i][j][1], max(f[i-1][j][1], f[i-1][j][0]));
			int L = -inf, R = inf;
			dnw(i_, i-1, 0) {
				int y = p[i_];
				vmax(L, a[y]), vmin(R, b[y]);
				if(L >= R) break;
				vmax(f[i][j][1], f[i_][j-1][1] + (R - L));
			}
		}
	}
	
	vector<vector<int> > h(m+1, vector<int>(2));
	upw(o, 0, 1) {
		upw(i, 0, m) upw(j, 0, i)
			vmax(h[i][o], f[(int)p.size()][j][o] + max(g[i-j][0], g[i-j][1]));
	}
	return h;
}

void WaterM() {
	cin >> n >> m;
	upw(i, 1, n) cin >> a[i] >> b[i];
	
	vector<int> p;
	upw(i, 1, n) p.push_back(i);
	
	auto f = solve(p);
	cout << f[m][0] << '\n';
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	while(_--) WaterM();
	return 0;
}


posted @ 2025-11-09 17:46  Water_M  阅读(9)  评论(0)    收藏  举报