Loading

luogu P14259 兄妹(siblings)

题目大意

\(n\) 本书,其中第 \(i\) 本在 \((x_i,y_i)\),下标从 \(1\) 开始。
两个人从 \((0,0)\) 同时出发,每次移动一格,求在访问一遍所有书所在位置的方案中,两人所用时间较大者的最小值(访问不耗时)
\(1\leq n\leq 1e5\)\(0\leq x_i,y_i\leq 500\)

Sol

首先需要发现一件事情,即每一行的所有书只会由一个人访问。
考虑如果一行中一人访问一部分书,另一个人访问另一部分,一定有人访问的书可以被另一个人顺手访问

知道了这个,就可以发现 \(x\leq 500\) 这极小的范围。
在暴力分中,我们通过 \(2^n\) 暴力每本书被谁拿走,容易发现这可以用 DP 代替(贪心不能处理这种情况)

所以我们考虑怎么设计 DP 状态。
容易发现,第 \(r\) 行的访问用时实际上是 \(2\times \max_{x_i=r} y_i\) 但是所有行的 \(\max\) 我们是可以求总和的,这样只要考虑第一个人去哪几行,另一个人就可以顺便确定。
这时候,访问最后一行的任务一定要落到某个人身上,由于可以对某个子集取反,即 DP 不会漏掉状态,所以可以直接派给第一个人。
这时候 DP 状态就明了了,令 \(f_{i,j}\) 表示从 \([1,i]\) 中取,能否取出 \(j\) 的总代价(我们需要每一种状态是否可行,否则会漏状态)。
转移就是 \(f_{i,j}\ |= f_{i-1,j}\ |\ f_{i-1,j-g_i}\),其中 \(g[i]\) 为第 \(i\) 行的 \(y\) 的最大值。
\(j\) 的范围?其实就是 \(\sum g_i\),也就是 \(500\times 500=2.5e5\)
统计答案的话,由于第 \(i\) 行必须选,可以看作第二个人在行上走的最远路程,即 \(res=\min\{2\times \max\{ i+j,m+sumc-j \} \}\)(注意把 \(2\) 放外面了)

这样子只能获得 \(90\) 分,我们需要用滚动数组优化一下空间。

Code

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5+10 , M = 510;

int n , m , k;
int g[M];
bool f[M*M]; 

void slove() {
	memset(g , 0 , sizeof g);
	memset(f , 0 , sizeof f);
	cin >> n; m = k = 0;
	for(int i = 1 ; i <= n ; i ++) {
		int r , c; cin >> r >> c;
		g[r] = max(g[r] , c);
		m = max(m , r);
	}
	
	for(int i = 1 ; i <= m ; i ++)
		k += g[i];
	
	int res = 0x3f3f3f3f , s = 0; 
	f[0] = true;
	for(int i = 1 ; i < m ; i ++) {
		if(!g[i]) continue;
		for(int j = s ; j >= 0 ; j --)
			f[j+g[i]] |= f[j];
		s += g[i];
		for(int j = 0 ; j <= s ; j ++)
			if(f[j])
				res = min(res , 2*max(i+j , m+k-j));
	}
	cout << res << '\n';
	return;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	
	int T; cin >> T;
	while(T --) slove();
	return 0;
}

其中 \(s\) 是为了减少一点常数,要不然容易 TLE

posted @ 2025-10-20 18:40  lyr2023  阅读(9)  评论(0)    收藏  举报