分治
HDU7172
平面上有 \(n\) 个点,第 \(i\) 个点的坐标为 \((x_i,y_i)\),并有一个参数 \(w_i\),表示另一个点 \(j\) 到这个点的收益为 \(\min(dis_{i,j},w_i)\)。有 \(q\) 次询问,每次给出一个点的坐标,求这个点到哪个点的收益最大。
\(n,q \le 5 \times 10^5,x_i,y_i,w_i \le 10^9\)
赛时看到这个题,首先想到的是“曼哈顿距离转化为切比雪夫距离”(总是有好性质)然后我们惊讶地发现,最大收益的式子变成了:
这个东西 \(\max\) 套 \(\max\),可以两个维度分开考虑!(切比雪夫就是神)
那么我们问题转化为了:给定 \(x\),求 \(\max \limits_{i}\{\max(|x_i-x|,w_i)\}\)。
发现对于每一个 \(i\),当 \(x \le x_i-w_i\) 或者 \(x \ge x_i+w_i\) 的时候,生效的是 \(w_i\);反之是距离。可以用双指针,先进行离散化,然后 \(i\) 跑一遍离散化的值域,并维护两个集合 \(s\) 和 \(t\)。一开始集合 \(s\) 中有所有 \(w\) 的值。遇到 \(x_i-w_i\) 的时候把 \(s\) 中 \(w_i\) 弹出,在 \(t\) 中插入 \(x_i\),遇到 \(x_i+w_i\) 相反。遇到询问的时候,它的答案就是 \(\max\{s_{\max},\max\{|x_q-t_{\max}|,|x_q-t_{\min}|\}\}\)。
时间复杂度 \(O(n \log n)\),使用 multiset 常数比较大。很遗憾被卡到了 \(4s\) 左右,还写了将近 \(4k\) 的代码。
我们先看看标算,再考虑卡卡本做法的常数。
标算的想法出发点是,发现去掉 \(w_i\) 之后是一个经典题:求平面上距离给定点的曼哈顿距离最大的点。这是可以 \(O(n)\) 预处理,\(O(1)\) 查询的东西:
思路还是把绝对值换成 \(\max\),但是简单粗暴直接把绝对值拆开变成一个正数和一个负数的最大值了,而不是常用的曼哈顿转切比雪夫。
然后我们预处理 \(±x_i±y_i\) 这四个东西的最大值就可以了。
这道题加了 \(w\) 的限制。考虑对 \(w\) 进行从小到大排序,然后分治。
对于每个数 \(i\)。
对于每个询问点 \(q\),dfs(l,r)
计算点集区间 \((l,r)\) 内的贡献。我们取 \(mid\)。并 \(O(1)\) 求出 \(mid...n\) 中和 \(q\) 距离最大的一个的距离,记为 \(d\)。
为什么分治能维护时间复杂度呢,想象一下原来求一个答案我们要线性时间,现在如果是求答案可以只遍历 \((l,r)\) (通常分治只需要考虑区间内的元素)是不是肉眼可见的复杂度顿时就变低了呢?这一次每个点的询问是 \(O(1)\) 的,也是 \(\log n\) 次就找到了)
上一次我们分治是求出一个区间内所有东西的答案,这次我们是求出一个区间内所有东西的贡献。
(本质是每个元素的答案只需要 \(\log n\) 次就被找到了)
- 如果 \(w_{mid} < d\),那么 \(k...n\) 对答案的贡献至少为 \(w_{mid}\)。(因为取到 \(d\) 的点 \(j\),贡献为 \(\min\{w_j,d\}\),这俩东西都是 \(\ge w_{mid}\) 的)那么之前的由于上界是 \(w_{mid}\) 不会对答案产生贡献。可以缩小范围到 \((mid+1,n)\)。
- 如果 \(w_{mid} \ge d\),那么 \(k...n\) 对答案的贡献为 \(d\)(因为 \(k...n\) 的所有元素 \(i\) 都满足 \(w_i \ge dis_{i,q}\),那么每个元素的贡献都是 \(dis_{i,q}\))那么我们只需考虑 \(1,mid-1\) 的贡献即可。
代码长度只有 \(1k\),牛逼的分治!!!
不过我写了 \(2k\),跑了 \(300ms\) 左右。不得不说分治真的厉害
#include<bits/stdc++.h>
using namespace std;
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
struct town {int x, y, w;} to[100010];
bool cmp(town a, town b){return a.w < b.w;}
int qx[100010], qy[100010];
int a[100010], b[100010], c[100010], d[100010], ans[100010];
int dis(int mid, int now) {
return max({-qx[now] - qy[now] + a[mid],
-qx[now] + qy[now] + b[mid], qx[now] - qy[now] + c[mid],
qx[now] + qy[now] + d[mid]});
}
void dfs(int l, int r, int now){
if(l > r) {return;}
int mid = (l + r) >> 1; int dx = dis(mid, now);
if(to[mid].w < dx) {
ans[now] = max(ans[now], to[mid].w);
dfs(mid + 1, r, now);
}
else {
ans[now] = max(ans[now], dx);
dfs(l, mid - 1, now);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(NULL);
cout.tie(NULL);
//think twice,code once.
//think once,debug forever.
int t; cin >> t;
while(t--) {
int n, q; cin >> n >> q;
f(i, 1, n) cin >> to[i].x >> to[i].y >> to[i].w;
sort(to + 1, to + n + 1, cmp);
f(i, 1, q) cin >> qx[i] >> qy[i];
a[n + 1] = b[n + 1] = c[n + 1] = d[n + 1] = -inf;
for(int i = n; i >= 1; i--) {
a[i] = max(a[i + 1], to[i].x + to[i].y);
b[i] = max(b[i + 1], to[i].x - to[i].y);
c[i] = max(c[i + 1], -to[i].x + to[i].y);
d[i] = max(d[i + 1], -to[i].x - to[i].y);
}
f(i, 1, q) ans[i] = 0;
f(i, 1, q) dfs(1, n, i);
f(i, 1, q) cout << ans[i] << endl;
}
return 0;
}
upd:听说这个东西叫整体二分。
upd2:我的做法复杂度是对的,可以避免使用 multiset,转而使用优先队列来卡常:
我们再开一个桶数组表示一个元素出现的次数,以判断合法性。
插入元素:桶内该元素++,优先队列里加入该元素。
删除元素:桶内该元素--。
查询最大值:不断弹出元素直到这个元素在桶内不为0.(合法)
复杂度同样是 \(O(n \log n)\),但是二叉堆维护的东西比较少,常数比较小。
ARC067F
注意到,我们一旦选择了每个餐券要在哪里使用,那么从左到右走一遍需要吃的饭店即可,不需要走其他店铺。我们反过来又有性质:如果决定要走 \(l\) 到 \(r\),那么我们每个餐券一定选择 \(l\) 到 \(r\) 里面最好吃的店。那么我们可以设计 DP 状态:\(dp[i][j]\) 表示从 \(l\) 走到 \(r\) 可以获得的最大收益是多少。
这个东西状态数 \(O(n^2)\),转移 \(O(m)\)(可以用 RMQ 和前缀和求)显然过不去。
考虑怎么把平方优化成 \(\log\)(说着简单其实MMP)
发现一个单调性质:用 \(H_i\) 表示以 \(i\) 为右端点的最优左端点,那么 \(H_i\) 随 \(i\) 的增大而增大。考虑分治。(又一个平方优化技巧:想单调性)
证明:如果 \(H_i < H_j, i > j\),那么 \(dp[H_i][i] > dp[H_j][i]\),那么 \(dis(i,j) < gx(h_i,h_j-1)\),其中 \(dis\) 指两家餐馆之间的距离,\(gx\) 指的是这几家店的美味食品对整体美味度的贡献。考虑 \(dp[H_i][j]\) 和 \(dp[H_j][j]\) 的关系。发现差值是 \(dis(i,j) - gx'(h_i,h_j-1)\)。这个 \(gx'\) 是右端点为 \(j\) 的时候这几家店的美味食品对整体美味度的贡献。显然 \(gx'(h_i,h_j-1)>gx(h_i,h_j-1)\)。那么 \(dp[H_i][j] > dp[H_j][j]\) 成立,那么 \(H_j\) 非最优,与假设矛盾,故原命题成立。
\(work(l,r,x,y)\) 表示我们要求出 \([l,r]\) 内的 \(H_i\),范围确定在了 \([x,y]\)。每次查找的时间复杂度为 \(O(y-x+1)\)。查找结果为 \(H_{mid}\),那么可以分治为 \(work(l,mid-1,x,H_{mid})\) 和 \(work(mid+1,r,H_{mid},y)\)。入口为 \(work(1,n,1,n)\)。这样做每一个数不超过 \(\log n\) 次就算好了,时间复杂度成功优化到 \(O(n \log n)\)。
实现上还有一个细节:分治出口是 \(l \ge r\),我因为写了 \(l==r\) 错了(当\(l==r-1\) 的时候 \(mid==l\) 就会出现这种情况)。以后做题的时候能判就要判,宁可错杀一千,不可放过一数。
听说这叫决策单调性优化。
还有,ST表贺板子可以,一定要注意定义域是否和板子里的一样。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e9;
int n, m;
int a[5010],s[5010],b[5010][210];
int st[210][5010][30];
int h[5010], v[5010];
void ST_prework() {
f(i,1,m){
f(j,1,n)st[i][j][0]=b[j][i];
int mx = log(n) / log(2);
f(j, 1, mx) {
int mxi = n - (1 << j) + 1;
f(l, 1, mxi) {
st[i][l][j] = max(st[i][l][j - 1], st[i][l+(1<<(j-1))][j-1]);
}
}
}
}
int query(int i, int l, int r) {
int mx = log(r - l + 1) / log(2);
int ans;
ans = max(st[i][l][mx], st[i][r-(1<<mx)+1][mx]);
return ans;
}
int calc(int x, int l, int r) {
int qv = 0, hh = 0;
f(qh, l, r) {
int tmp = 0;
if(qh > x) break;
f(i, 1, m) {
tmp += query(i, qh, x);
}
tmp -= (s[x-1] - s[qh - 1]);
if(qv < tmp) {qv = tmp; hh = qh;}
}
v[x] = qv; h[x] = hh;
return hh;
}
void dfs(int l, int r, int x, int y){
int mid = (l+r)>>1;
int hmid = calc(mid, x, y);
if(l >= r) return;
dfs(l, mid - 1, x, hmid); dfs(mid + 1, r, hmid, y);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> m;
f(i,1 ,n-1) {cin >> a[i];s[i]=s[i-1]+a[i];}
f(i, 1, n)f(j,1,m)cin>>b[i][j];
ST_prework();
dfs(1, n, 1, n);
int ans = 0;
f(i, 1, n) ans = max(ans, v[i]);
cout << ans << endl;
return 0;
}