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